Add test suites for dht bucket, kademlia and key

pull/236/head
Michael 5 years ago
parent 7296ebcbe8
commit 03d56c1591
No known key found for this signature in database
GPG Key ID: 2D51757B47E2434C

@ -609,8 +609,9 @@ set(TEST_SRC
# actual test cases
test/crypto/test_llarp_crypto_types.cpp
test/crypto/test_llarp_crypto.cpp
test/dht/test_llarp_dht_bucket.cpp
test/dht/test_llarp_dht_kademlia.cpp
test/dht/test_llarp_dht_key.cpp
test/dht/test_llarp_dht.cpp
test/dns/test_llarp_dns_dns.cpp
test/exit/test_llarp_exit_context.cpp
test/link/test_llarp_link.cpp

@ -1,7 +1,6 @@
#ifndef LLARP_DHT_BUCKET_HPP
#define LLARP_DHT_BUCKET_HPP
#include <crypto/crypto.hpp>
#include <dht/kademlia.hpp>
#include <dht/key.hpp>
@ -17,28 +16,47 @@ namespace llarp
struct Bucket
{
using BucketStorage_t = std::map< Key_t, Val_t, XorMetric >;
using Random_t = std::function< uint64_t() >;
Bucket(const Key_t& us) : nodes(XorMetric(us)){};
Bucket(const Key_t& us, Random_t r) : nodes(XorMetric(us)), random(r){};
size_t
Size() const
size() const
{
return nodes.size();
}
struct SetIntersector
{
bool
operator()(const typename BucketStorage_t::value_type& lhs,
const Key_t& rhs)
{
return lhs.first < rhs;
}
bool
operator()(const Key_t& lhs,
const typename BucketStorage_t::value_type& rhs)
{
return lhs < rhs.first;
}
};
bool
GetRandomNodeExcluding(Key_t& result,
const std::set< Key_t >& exclude) const
{
std::vector< Key_t > candidates;
for(const auto& item : nodes)
std::vector< typename BucketStorage_t::value_type > candidates;
std::set_difference(nodes.begin(), nodes.end(), exclude.begin(),
exclude.end(), std::back_inserter(candidates),
SetIntersector());
if(candidates.empty())
{
if(exclude.find(item.first) == exclude.end())
candidates.push_back(item.first);
}
if(candidates.size() == 0)
return false;
result = candidates[llarp::randint() % candidates.size()];
}
result = candidates[random() % candidates.size()].first;
return true;
}
@ -62,7 +80,7 @@ namespace llarp
bool
GetManyRandom(std::set< Key_t >& result, size_t N) const
{
if(nodes.size() < N)
if(nodes.size() < N || nodes.empty())
{
llarp::LogWarn("Not enough dht nodes, have ", nodes.size(), " want ",
N);
@ -70,10 +88,10 @@ namespace llarp
}
if(nodes.size() == N)
{
for(const auto& node : nodes)
{
result.insert(node.first);
}
std::transform(nodes.begin(), nodes.end(),
std::inserter(result, result.end()),
[](const auto& a) { return a.first; });
return true;
}
size_t expecting = N;
@ -81,31 +99,15 @@ namespace llarp
while(N)
{
auto itr = nodes.begin();
std::advance(itr, llarp::randint() % sz);
std::advance(itr, random() % sz);
if(result.insert(itr->first).second)
{
--N;
}
}
return result.size() == expecting;
}
bool
GetManyNearExcluding(const Key_t& target, std::set< Key_t >& result,
size_t N, const std::set< Key_t >& exclude) const
{
std::set< Key_t > s;
for(const auto& k : exclude)
s.insert(k);
Key_t peer;
while(N--)
{
if(!FindCloseExcluding(target, peer, s))
return false;
s.insert(peer);
result.insert(peer);
}
return true;
}
bool
FindCloseExcluding(const Key_t& target, Key_t& result,
const std::set< Key_t >& exclude) const
@ -117,7 +119,9 @@ namespace llarp
for(const auto& item : nodes)
{
if(exclude.count(item.first))
{
continue;
}
auto curDist = item.first ^ target;
if(curDist < mindist)
@ -129,12 +133,33 @@ namespace llarp
return mindist < maxdist;
}
bool
GetManyNearExcluding(const Key_t& target, std::set< Key_t >& result,
size_t N, const std::set< Key_t >& exclude) const
{
std::set< Key_t > s(exclude.begin(), exclude.end());
Key_t peer;
while(N--)
{
if(!FindCloseExcluding(target, peer, s))
{
return false;
}
s.insert(peer);
result.insert(peer);
}
return true;
}
void
PutNode(const Val_t& val)
{
auto itr = nodes.find(val.ID);
if(itr == nodes.end() || itr->second < val)
{
nodes[val.ID] = val;
}
}
void
@ -142,7 +167,9 @@ namespace llarp
{
auto itr = nodes.find(key);
if(itr != nodes.end())
{
nodes.erase(itr);
}
}
bool
@ -151,7 +178,14 @@ namespace llarp
return nodes.find(key) != nodes.end();
}
void
Clear()
{
nodes.clear();
}
BucketStorage_t nodes;
Random_t random;
};
} // namespace dht
} // namespace llarp

@ -271,8 +271,8 @@ namespace llarp
{
router = r;
ourKey = us;
nodes = new Bucket< RCNode >(ourKey);
services = new Bucket< ISNode >(ourKey);
nodes = new Bucket< RCNode >(ourKey, llarp::randint);
services = new Bucket< ISNode >(ourKey, llarp::randint);
llarp::LogDebug("initialize dht with key ", ourKey);
// start exploring
@ -669,14 +669,14 @@ namespace llarp
if(!nodes)
return false;
size_t nodeCount = nodes->Size();
size_t nodeCount = nodes->size();
if(nodeCount == 0)
{
llarp::LogError(
"cannot handle exploritory router lookup, no dht peers");
return false;
}
llarp::LogDebug("We have ", nodes->Size(),
llarp::LogDebug("We have ", nodes->size(),
" connected nodes into the DHT");
// ourKey should never be in the connected list
// requester is likely in the connected list

@ -7,19 +7,6 @@ namespace llarp
{
namespace dht
{
/*
struct IntroSetLookupInformer
{
llarp::Router* router;
service::Address target;
void
SendReply(const llarp::routing::IMessage* msg)
{
}
};
*/
FindIntroMessage::~FindIntroMessage()
{
}

@ -1,123 +0,0 @@
#include <dht/bucket.hpp>
#include <dht/key.hpp>
#include <dht/node.hpp>
#include <gtest/gtest.h>
using Key_t = llarp::dht::Key_t;
class KademliaDHTTest : public ::testing::Test
{
public:
KademliaDHTTest()
{
}
~KademliaDHTTest()
{
}
void
SetUp()
{
us.Fill(16);
nodes = new llarp::dht::Bucket< llarp::dht::RCNode >(us);
size_t numNodes = 10;
byte_t fill = 1;
while(numNodes)
{
llarp::dht::RCNode n;
n.ID.Fill(fill);
nodes->PutNode(n);
--numNodes;
++fill;
}
}
void
TearDown()
{
delete nodes;
}
llarp::dht::Bucket< llarp::dht::RCNode >* nodes = nullptr;
llarp::dht::Key_t us;
};
TEST_F(KademliaDHTTest, TestBucketFindClosest)
{
llarp::dht::Key_t result;
llarp::dht::Key_t target;
llarp::dht::Key_t oldResult;
target.Fill(5);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_TRUE(target == result);
oldResult = result;
target.Fill(0xf5);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_TRUE(oldResult == result);
};
TEST_F(KademliaDHTTest, TestBucketOperators)
{
llarp::dht::Key_t zero;
llarp::dht::Key_t one;
llarp::dht::Key_t three;
zero.Zero();
one.Fill(1);
three.Fill(3);
ASSERT_TRUE(zero < one);
ASSERT_TRUE(zero < three);
ASSERT_FALSE(zero > one);
ASSERT_FALSE(zero > three);
ASSERT_TRUE(zero != three);
ASSERT_FALSE(zero == three);
ASSERT_TRUE((zero ^ one) == one);
ASSERT_TRUE(one < three);
ASSERT_TRUE(three > one);
ASSERT_TRUE(one != three);
ASSERT_FALSE(one == three);
ASSERT_TRUE((one ^ three) == (three ^ one));
};
TEST_F(KademliaDHTTest, TestBucketRandomized_1000)
{
size_t moreNodes = 100;
while(moreNodes--)
{
llarp::dht::RCNode n;
n.ID.Randomize();
nodes->PutNode(n);
}
const size_t count = 1000;
size_t left = count;
while(left--)
{
llarp::dht::Key_t result;
llarp::dht::Key_t target;
llarp::dht::Key_t expect;
target.Randomize();
expect = target;
ASSERT_TRUE(nodes->FindClosest(target, result));
if(target == result)
{
ASSERT_FALSE((result ^ target) < (expect ^ target));
ASSERT_FALSE((result ^ target) != (expect ^ target));
ASSERT_TRUE((result ^ target) == (expect ^ target));
}
else
{
Key_t dist = result ^ target;
Key_t oldDist = expect ^ target;
ASSERT_TRUE((result ^ target) != (expect ^ target));
if((result ^ target) < (expect ^ target))
{
std::cout << "result=" << result << "expect=" << expect << std::endl;
std::cout << dist << ">=" << oldDist << "iteration=" << (count - left)
<< std::endl;
ASSERT_TRUE(false);
}
ASSERT_FALSE((result ^ target) == (expect ^ target));
}
}
};

@ -0,0 +1,370 @@
#include <dht/bucket.hpp>
#include <dht/key.hpp>
#include <dht/node.hpp>
#include <gtest/gtest.h>
using Key_t = llarp::dht::Key_t;
using Value_t = llarp::dht::RCNode;
using Bucket_t = llarp::dht::Bucket< Value_t >;
class TestDhtBucket : public ::testing::Test
{
public:
TestDhtBucket() : randInt(0)
{
us.Fill(16);
nodes = std::make_unique< Bucket_t >(us, [&]() { return randInt++; });
size_t numNodes = 10;
byte_t fill = 1;
while(numNodes)
{
Value_t n;
n.ID.Fill(fill);
nodes->PutNode(n);
--numNodes;
++fill;
}
}
uint64_t randInt;
llarp::dht::Key_t us;
std::unique_ptr< Bucket_t > nodes;
};
TEST_F(TestDhtBucket, simple_cycle)
{
// Empty the current bucket.
nodes->Clear();
// Create a simple value, and add it to the bucket.
Value_t val;
val.ID.Fill(1);
nodes->PutNode(val);
// Verify the value is in the bucket
ASSERT_TRUE(nodes->HasNode(val.ID));
ASSERT_EQ(1u, nodes->size());
// Verify after deletion, the value is no longer in the bucket
nodes->DelNode(val.ID);
ASSERT_FALSE(nodes->HasNode(val.ID));
// Verify deleting again succeeds;
nodes->DelNode(val.ID);
ASSERT_FALSE(nodes->HasNode(val.ID));
}
TEST_F(TestDhtBucket, get_random_node_excluding)
{
// Empty the current bucket.
nodes->Clear();
// We expect not to find anything
Key_t result;
std::set< Key_t > excludeSet;
ASSERT_FALSE(nodes->GetRandomNodeExcluding(result, excludeSet));
// Create a simple value.
Value_t val;
val.ID.Fill(1);
// Add the simple value to the exclude set
excludeSet.insert(val.ID);
ASSERT_FALSE(nodes->GetRandomNodeExcluding(result, excludeSet));
// Add the simple value to the bucket
nodes->PutNode(val);
ASSERT_FALSE(nodes->GetRandomNodeExcluding(result, excludeSet));
excludeSet.clear();
ASSERT_TRUE(nodes->GetRandomNodeExcluding(result, excludeSet));
ASSERT_EQ(val.ID, result);
// Add an element to the exclude set which isn't the bucket.
Key_t other;
other.Fill(0xff);
excludeSet.insert(other);
ASSERT_TRUE(nodes->GetRandomNodeExcluding(result, excludeSet));
ASSERT_EQ(val.ID, result);
// Add a node which is in both bucket and excludeSet
Value_t nextVal;
nextVal.ID.Fill(0xAA);
excludeSet.insert(nextVal.ID);
nodes->PutNode(nextVal);
ASSERT_TRUE(nodes->GetRandomNodeExcluding(result, excludeSet));
ASSERT_EQ(val.ID, result);
// Clear the excludeSet - we should still have 2 nodes in the bucket
excludeSet.clear();
randInt = 0;
ASSERT_TRUE(nodes->GetRandomNodeExcluding(result, excludeSet));
ASSERT_EQ(val.ID, result);
// Set the random value to be 1, we should get the other node.
randInt = 1;
ASSERT_TRUE(nodes->GetRandomNodeExcluding(result, excludeSet));
ASSERT_EQ(nextVal.ID, result);
// Set the random value to be 100, we should get the first node.
randInt = 100;
ASSERT_TRUE(nodes->GetRandomNodeExcluding(result, excludeSet));
ASSERT_EQ(val.ID, result);
}
TEST_F(TestDhtBucket, find_closest)
{
// Empty the current bucket.
nodes->Clear();
// We expect not to find anything
Key_t target;
target.Fill(0xF0);
Key_t result;
ASSERT_FALSE(nodes->FindClosest(target, result));
// Add a node to the bucket
Value_t first;
first.ID.Zero();
nodes->PutNode(first);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_EQ(result, first.ID);
// Add another node to the bucket, closer to the target
Value_t second;
second.ID.Fill(0x10);
nodes->PutNode(second);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_EQ(result, second.ID);
// Add a third node to the bucket, closer to the target
Value_t third;
third.ID.Fill(0x20);
nodes->PutNode(third);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_EQ(result, third.ID);
// Add a fourth node to the bucket, greater than the target
Value_t fourth;
fourth.ID.Fill(0xF1);
nodes->PutNode(fourth);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_EQ(result, fourth.ID);
// Add a fifth node to the bucket, equal to the target
Value_t fifth;
fifth.ID.Fill(0xF0);
nodes->PutNode(fifth);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_EQ(result, fifth.ID);
}
TEST_F(TestDhtBucket, get_many_random)
{
// Empty the current bucket.
nodes->Clear();
// Verify behaviour with empty node set
std::set< Key_t > result;
ASSERT_FALSE(nodes->GetManyRandom(result, 0));
ASSERT_FALSE(nodes->GetManyRandom(result, 1));
// Add 5 nodes to the bucket
std::set< Value_t > curValues;
std::set< Key_t > curKeys;
for(byte_t i = 0x00; i < 0x05; ++i)
{
Value_t v;
v.ID.Fill(i);
ASSERT_TRUE(curKeys.insert(v.ID).second);
nodes->PutNode(v);
}
// Fetching more than the current size fails
ASSERT_EQ(5u, nodes->size());
ASSERT_FALSE(nodes->GetManyRandom(result, nodes->size() + 1));
// Fetching the current size succeeds
ASSERT_TRUE(nodes->GetManyRandom(result, nodes->size()));
ASSERT_EQ(curKeys, result);
// Fetching a subset succeeds.
// Note we hack this by "fixing" the random number generator
result.clear();
ASSERT_TRUE(nodes->GetManyRandom(result, 1u));
ASSERT_EQ(1u, result.size());
ASSERT_EQ(*curKeys.begin(), *result.begin());
randInt = 0;
result.clear();
ASSERT_TRUE(nodes->GetManyRandom(result, nodes->size() - 1));
ASSERT_EQ(nodes->size() - 1, result.size());
ASSERT_EQ(std::set< Key_t >(++curKeys.rbegin(), curKeys.rend()), result);
}
TEST_F(TestDhtBucket, find_close_excluding)
{
// Empty the current bucket.
nodes->Clear();
Key_t target;
target.Zero();
std::set< Key_t > exclude;
Key_t result;
// Empty node + exclude set fails
ASSERT_FALSE(nodes->FindCloseExcluding(target, result, exclude));
Value_t first;
first.ID.Fill(0xF0);
exclude.insert(first.ID);
// Empty nodes fails
ASSERT_FALSE(nodes->FindCloseExcluding(target, result, exclude));
// Nodes and exclude set match
nodes->PutNode(first);
ASSERT_FALSE(nodes->FindCloseExcluding(target, result, exclude));
// Exclude set empty
exclude.clear();
ASSERT_TRUE(nodes->FindCloseExcluding(target, result, exclude));
result = first.ID;
Value_t second;
second.ID.Fill(0x01);
nodes->PutNode(second);
ASSERT_TRUE(nodes->FindCloseExcluding(target, result, exclude));
result = second.ID;
exclude.insert(second.ID);
ASSERT_TRUE(nodes->FindCloseExcluding(target, result, exclude));
result = first.ID;
}
TEST_F(TestDhtBucket, find_many_near_excluding)
{
// Empty the current bucket.
nodes->Clear();
Key_t target;
target.Zero();
std::set< Key_t > exclude;
std::set< Key_t > result;
// Empty node + exclude set, with size 0 succeeds
ASSERT_TRUE(nodes->GetManyNearExcluding(target, result, 0, exclude));
ASSERT_EQ(0u, result.size());
// Empty node + exclude set fails
ASSERT_FALSE(nodes->GetManyNearExcluding(target, result, 1, exclude));
Value_t first;
first.ID.Fill(0xF0);
exclude.insert(first.ID);
// Empty nodes fails
ASSERT_FALSE(nodes->GetManyNearExcluding(target, result, 1, exclude));
// Nodes and exclude set match
nodes->PutNode(first);
ASSERT_FALSE(nodes->GetManyNearExcluding(target, result, 1, exclude));
// Single node succeeds
exclude.clear();
ASSERT_TRUE(nodes->GetManyNearExcluding(target, result, 1, exclude));
ASSERT_EQ(result, std::set< Key_t >({first.ID}));
// Trying to grab 2 nodes from a 1 node set fails
result.clear();
ASSERT_FALSE(nodes->GetManyNearExcluding(target, result, 2, exclude));
// two nodes finds closest
Value_t second;
second.ID.Fill(0x01);
nodes->PutNode(second);
result.clear();
ASSERT_TRUE(nodes->GetManyNearExcluding(target, result, 1, exclude));
ASSERT_EQ(result, std::set< Key_t >({second.ID}));
// 3 nodes finds 2 closest
Value_t third;
third.ID.Fill(0x02);
nodes->PutNode(third);
result.clear();
ASSERT_TRUE(nodes->GetManyNearExcluding(target, result, 2, exclude));
ASSERT_EQ(result, std::set< Key_t >({second.ID, third.ID}));
// 4 nodes, one in exclude set finds 2 closest
Value_t fourth;
fourth.ID.Fill(0x03);
nodes->PutNode(fourth);
exclude.insert(third.ID);
result.clear();
ASSERT_TRUE(nodes->GetManyNearExcluding(target, result, 2, exclude));
ASSERT_EQ(result, std::set< Key_t >({second.ID, fourth.ID}));
}
TEST_F(TestDhtBucket, TestBucketFindClosest)
{
llarp::dht::Key_t result;
llarp::dht::Key_t target;
target.Fill(5);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_EQ(target, result);
const llarp::dht::Key_t oldResult = result;
target.Fill(0xf5);
ASSERT_TRUE(nodes->FindClosest(target, result));
ASSERT_EQ(oldResult, result);
};
TEST_F(TestDhtBucket, TestBucketRandomized_1000)
{
size_t moreNodes = 100;
while(moreNodes--)
{
llarp::dht::RCNode n;
n.ID.Fill(randInt);
randInt++;
nodes->PutNode(n);
}
const size_t count = 1000;
size_t left = count;
while(left--)
{
llarp::dht::Key_t result;
llarp::dht::Key_t target;
target.Randomize();
const llarp::dht::Key_t expect = target;
ASSERT_TRUE(nodes->FindClosest(target, result));
if(target == result)
{
ASSERT_GE(result ^ target, expect ^ target);
ASSERT_EQ(result ^ target, expect ^ target);
ASSERT_EQ(result ^ target, expect ^ target);
}
else
{
Key_t dist = result ^ target;
Key_t oldDist = expect ^ target;
ASSERT_NE(result ^ target, expect ^ target);
ASSERT_GE(result ^ target, expect ^ target)
<< "result=" << result << "expect=" << expect << std::endl
<< dist << ">=" << oldDist << "iteration=" << (count - left);
ASSERT_NE(result ^ target, expect ^ target);
}
}
};

@ -0,0 +1,88 @@
#include <dht/kademlia.hpp>
#include <gtest/gtest.h>
using llarp::dht::Key_t;
using Array = std::array< byte_t, Key_t::SIZE >;
struct XorMetricData
{
Array us;
Array left;
Array right;
bool result;
XorMetricData(const Array& u, const Array& l, const Array& r, bool res)
: us(u), left(l), right(r), result(res)
{
}
};
std::ostream&
operator<<(std::ostream& stream, const XorMetricData& x)
{
stream << int(x.us[0]) << " " << int(x.left[0]) << " " << int(x.right[0])
<< " " << std::boolalpha << x.result;
return stream;
}
struct XorMetric : public ::testing::TestWithParam< XorMetricData >
{
};
TEST_P(XorMetric, test)
{
auto d = GetParam();
ASSERT_EQ(llarp::dht::XorMetric{Key_t{d.us}}(Key_t{d.left}, Key_t{d.right}),
d.result);
}
std::vector< XorMetricData >
makeData()
{
std::vector< XorMetricData > result;
Array zero;
zero.fill(0);
Array one;
one.fill(1);
Array two;
two.fill(2);
Array three;
three.fill(3);
result.emplace_back(zero, zero, zero, false);
result.emplace_back(zero, zero, one, true);
result.emplace_back(zero, zero, two, true);
result.emplace_back(zero, one, zero, false);
result.emplace_back(zero, one, one, false);
result.emplace_back(zero, one, two, true);
result.emplace_back(zero, two, zero, false);
result.emplace_back(zero, two, one, false);
result.emplace_back(zero, two, two, false);
result.emplace_back(one, zero, zero, false);
result.emplace_back(one, zero, one, false);
result.emplace_back(one, zero, two, true);
result.emplace_back(one, one, zero, true);
result.emplace_back(one, one, one, false);
result.emplace_back(one, one, two, true);
result.emplace_back(one, two, zero, false);
result.emplace_back(one, two, one, false);
result.emplace_back(one, two, two, false);
result.emplace_back(two, zero, zero, false);
result.emplace_back(two, zero, one, true);
result.emplace_back(two, zero, two, false);
result.emplace_back(two, one, zero, false);
result.emplace_back(two, one, one, false);
result.emplace_back(two, one, two, false);
result.emplace_back(two, two, zero, true);
result.emplace_back(two, two, one, true);
result.emplace_back(two, two, two, false);
return result;
}
INSTANTIATE_TEST_CASE_P(TestDhtXorMetric, XorMetric,
::testing::ValuesIn(makeData()));

@ -98,3 +98,26 @@ TEST(TestDhtKey, XOR)
ASSERT_EQ(dht::Key_t(xorResult),
dht::Key_t(seqArray) ^ dht::Key_t(fullArray));
}
TEST(TestDhtKey, TestBucketOperators)
{
dht::Key_t zero;
dht::Key_t one;
dht::Key_t three;
zero.Zero();
one.Fill(1);
three.Fill(3);
ASSERT_LT(zero, one);
ASSERT_LT(zero, three);
ASSERT_FALSE(zero > one);
ASSERT_FALSE(zero > three);
ASSERT_NE(zero, three);
ASSERT_FALSE(zero == three);
ASSERT_EQ(zero ^ one, one);
ASSERT_LT(one, three);
ASSERT_GT(three, one);
ASSERT_NE(one, three);
ASSERT_FALSE(one == three);
ASSERT_EQ(one ^ three, three ^ one);
};

Loading…
Cancel
Save