Merge pull request #259 from michael-loki/more_dht_tests

More dht tests
pull/280/head
Ryan Tharp 5 years ago committed by GitHub
commit 5769e5c716
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -626,6 +626,7 @@ set(DNS_SRC
set(TEST_SRC
# helpers
test/main.cpp
test/crypto/mock_crypto.cpp
test/dht/mock_context.cpp
test/test_util.cpp
# actual test cases
@ -635,6 +636,9 @@ set(TEST_SRC
test/dht/test_llarp_dht_explorenetworkjob.cpp
test/dht/test_llarp_dht_kademlia.cpp
test/dht/test_llarp_dht_key.cpp
test/dht/test_llarp_dht_node.cpp
test/dht/test_llarp_dht_serviceaddresslookup.cpp
test/dht/test_llarp_dht_taglookup.cpp
test/dht/test_llarp_dht_tx.cpp
test/dht/test_llarp_dht_txowner.cpp
test/dns/test_llarp_dns_dns.cpp

@ -31,16 +31,18 @@ namespace llarp
/// on behalf of whoasked request introset for target from dht router with
/// key askpeer
virtual void
LookupIntroSetRecursive(
const service::Address& target, const Key_t& whoasked,
uint64_t whoaskedTX, const Key_t& askpeer, uint64_t R,
service::IntroSetLookupHandler result = nullptr) = 0;
LookupIntroSetRecursive(const service::Address& target,
const Key_t& whoasked, uint64_t whoaskedTX,
const Key_t& askpeer, uint64_t R,
service::IntroSetLookupHandler result =
service::IntroSetLookupHandler()) = 0;
virtual void
LookupIntroSetIterative(
const service::Address& target, const Key_t& whoasked,
uint64_t whoaskedTX, const Key_t& askpeer,
service::IntroSetLookupHandler result = nullptr) = 0;
LookupIntroSetIterative(const service::Address& target,
const Key_t& whoasked, uint64_t whoaskedTX,
const Key_t& askpeer,
service::IntroSetLookupHandler result =
service::IntroSetLookupHandler()) = 0;
virtual std::set< service::IntroSet >
FindRandomIntroSetsWithTagExcluding(

@ -39,8 +39,16 @@ namespace llarp
ServiceAddressLookup::GetNextPeer(Key_t &next,
const std::set< Key_t > &exclude)
{
Key_t k = target.ToKey();
return parent->Nodes()->FindCloseExcluding(k, next, exclude);
Key_t k = target.ToKey();
auto nodes = parent->Nodes();
if(nodes)
{
return nodes->FindCloseExcluding(k, next, exclude);
}
else
{
return false;
}
}
void

@ -33,25 +33,17 @@ namespace llarp
void
TagLookup::SendReply()
{
std::set< service::IntroSet > found;
for(const auto &remoteTag : valuesFound)
{
found.insert(remoteTag);
}
std::set< service::IntroSet > found(valuesFound.begin(),
valuesFound.end());
// collect our local values if we haven't hit a limit
if(found.size() < 2)
{
for(const auto &localTag :
parent->FindRandomIntroSetsWithTagExcluding(target, 1, found))
{
found.insert(localTag);
}
}
std::vector< service::IntroSet > values;
for(const auto &introset : found)
{
values.push_back(introset);
auto tags =
parent->FindRandomIntroSetsWithTagExcluding(target, 1, found);
std::copy(tags.begin(), tags.end(), std::inserter(found, found.end()));
}
std::vector< service::IntroSet > values(found.begin(), found.end());
parent->DHTSendTo(whoasked.node.as_array(),
new GotIntroMessage(values, whoasked.txid));
}

@ -338,8 +338,8 @@ namespace llarp
return false;
}
f.seekg(0, std::ios::end);
size_t l = f.tellg();
if(l > tmp.size())
auto l = f.tellg();
if(l > static_cast< std::streamoff >(sizeof tmp))
return false;
f.seekg(0, std::ios::beg);
f.read((char *)tmp.data(), l);

@ -115,16 +115,22 @@ namespace llarp
copy = *this;
copy.Z.Zero();
if(!copy.BEncode(&buf))
{
return false;
}
// rewind and resize buffer
buf.sz = buf.cur - buf.base;
buf.cur = buf.base;
if(!A.Verify(crypto, buf, Z))
{
return false;
}
// validate PoW
using namespace std::placeholders;
if(W && !W->IsValid(std::bind(&Crypto::shorthash, crypto, _1, _2), now))
{
return false;
}
// valid timestamps
// add max clock skew
now += MAX_INTROSET_TIME_DELTA;

@ -0,0 +1 @@
#include <crypto/mock_crypto.hpp>

@ -0,0 +1,76 @@
#ifndef TEST_LLARP_CRYPTO_MOCK_CRYPTO
#define TEST_LLARP_CRYPTO_MOCK_CRYPTO
#include <crypto/crypto.hpp>
#include <gmock/gmock.h>
namespace llarp
{
namespace test
{
struct MockCrypto final : public Crypto
{
MOCK_METHOD3(xchacha20,
bool(const llarp_buffer_t &, const SharedSecret &,
const TunnelNonce &));
MOCK_METHOD4(xchacha20_alt,
bool(const llarp_buffer_t &, const llarp_buffer_t &,
const SharedSecret &, const byte_t *));
MOCK_METHOD4(dh_client,
bool(SharedSecret &, const PubKey &, const SecretKey &,
const TunnelNonce &));
MOCK_METHOD4(dh_server,
bool(SharedSecret &, const PubKey &, const SecretKey &,
const TunnelNonce &));
MOCK_METHOD4(transport_dh_client,
bool(SharedSecret &, const PubKey &, const SecretKey &,
const TunnelNonce &));
MOCK_METHOD4(transport_dh_server,
bool(SharedSecret &, const PubKey &, const SecretKey &,
const TunnelNonce &));
MOCK_METHOD2(hash, bool(byte_t *, const llarp_buffer_t &));
MOCK_METHOD2(shorthash, bool(ShortHash &, const llarp_buffer_t &));
MOCK_METHOD3(hmac,
bool(byte_t *, const llarp_buffer_t &,
const SharedSecret &));
MOCK_METHOD3(sign,
bool(Signature &, const SecretKey &,
const llarp_buffer_t &));
MOCK_METHOD3(verify,
bool(const PubKey &, const llarp_buffer_t &,
const Signature &));
MOCK_METHOD2(seed_to_secretkey,
bool(llarp::SecretKey &, const llarp::IdentitySecret &));
MOCK_METHOD1(randomize, void(const llarp_buffer_t &));
MOCK_METHOD2(randbytes, void(void *, size_t));
MOCK_METHOD1(identity_keygen, void(SecretKey &));
MOCK_METHOD1(encryption_keygen, void(SecretKey &));
MOCK_METHOD1(pqe_keygen, void(PQKeyPair &));
MOCK_METHOD3(pqe_decrypt,
bool(const PQCipherBlock &, SharedSecret &, const byte_t *));
MOCK_METHOD3(pqe_encrypt,
bool(PQCipherBlock &, SharedSecret &, const PQPubKey &));
};
} // namespace test
} // namespace llarp
#endif

@ -1,5 +1,5 @@
#ifndef TEST_LLARP_MOCK_CONTEXT
#define TEST_LLARP_MOCK_CONTEXT
#ifndef TEST_LLARP_DHT_MOCK_CONTEXT
#define TEST_LLARP_DHT_MOCK_CONTEXT
#include <dht/context.hpp>

@ -0,0 +1,117 @@
#include <dht/node.hpp>
#include <test_util.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
using namespace llarp;
using namespace ::testing;
using test::makeBuf;
struct TestDhtRCNode : public ::testing::Test
{
};
TEST_F(TestDhtRCNode, construct)
{
dht::RCNode node;
ASSERT_THAT(node.ID, Property(&dht::Key_t::IsZero, true));
node.ID.Fill(0xCA);
node.rc.last_updated = 101;
dht::RCNode other{node};
ASSERT_EQ(node.ID, other.ID);
ASSERT_EQ(node.rc, other.rc);
RouterContact contact;
contact.pubkey.Randomize();
dht::RCNode fromContact{contact};
ASSERT_EQ(fromContact.ID.as_array(), contact.pubkey.as_array());
}
TEST_F(TestDhtRCNode, lt)
{
dht::RCNode one;
dht::RCNode two;
dht::RCNode three;
dht::RCNode eqThree;
one.rc.last_updated = 1;
two.rc.last_updated = 2;
three.rc.last_updated = 3;
eqThree.rc.last_updated = 3;
// LT cases
ASSERT_THAT(one, Lt(two));
ASSERT_THAT(one, Lt(three));
ASSERT_THAT(one, Lt(eqThree));
ASSERT_THAT(two, Lt(three));
ASSERT_THAT(two, Lt(eqThree));
// !LT cases
ASSERT_THAT(one, Not(Lt(one)));
ASSERT_THAT(two, Not(Lt(one)));
ASSERT_THAT(two, Not(Lt(two)));
ASSERT_THAT(three, Not(Lt(one)));
ASSERT_THAT(three, Not(Lt(two)));
ASSERT_THAT(three, Not(Lt(three)));
ASSERT_THAT(three, Not(Lt(eqThree)));
}
struct TestDhtISNode : public ::testing::Test
{
};
TEST_F(TestDhtISNode, construct)
{
dht::ISNode node;
ASSERT_THAT(node.ID, Property(&dht::Key_t::IsZero, true));
node.ID.Fill(0xCA);
node.introset.K.Fill(0xDB);
dht::ISNode other{node};
ASSERT_EQ(node.ID, other.ID);
ASSERT_EQ(node.introset, other.introset);
service::IntroSet introSet;
introSet.K.Randomize();
introSet.A.UpdateAddr();
dht::ISNode fromIntro{introSet};
ASSERT_EQ(fromIntro.ID.as_array(), introSet.A.Addr().as_array());
}
TEST_F(TestDhtISNode, lt)
{
dht::ISNode one;
dht::ISNode two;
dht::ISNode three;
dht::ISNode eqThree;
one.introset.T = 1;
two.introset.T = 2;
three.introset.T = 3;
eqThree.introset.T = 3;
// LT cases
ASSERT_THAT(one, Lt(two));
ASSERT_THAT(one, Lt(three));
ASSERT_THAT(one, Lt(eqThree));
ASSERT_THAT(two, Lt(three));
ASSERT_THAT(two, Lt(eqThree));
// !LT cases
ASSERT_THAT(one, Not(Lt(one)));
ASSERT_THAT(two, Not(Lt(one)));
ASSERT_THAT(two, Not(Lt(two)));
ASSERT_THAT(three, Not(Lt(one)));
ASSERT_THAT(three, Not(Lt(two)));
ASSERT_THAT(three, Not(Lt(three)));
ASSERT_THAT(three, Not(Lt(eqThree)));
}

@ -0,0 +1,222 @@
#include <dht/serviceaddresslookup.hpp>
#include <crypto/mock_crypto.hpp>
#include <dht/mock_context.hpp>
#include <dht/messages/gotintro.hpp>
#include <service/IntroSet.hpp>
#include <test_util.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
using namespace llarp;
using namespace ::testing;
using test::makeBuf;
struct MockIntroSetHandler
{
MOCK_METHOD1(call, void(const std::vector< service::IntroSet > &));
};
static constexpr uint64_t EXPIRY = 1548503831ull;
struct TestDhtServiceAddressLookup : public ::testing::Test
{
test::MockCrypto crypto;
MockIntroSetHandler introsetHandler;
dht::Key_t ourKey;
dht::Key_t txKey;
uint64_t txId;
dht::TXOwner txOwner;
service::Address address;
test::MockContext context;
uint64_t r;
std::unique_ptr< dht::ServiceAddressLookup > serviceAddressLookup;
TestDhtServiceAddressLookup()
: ourKey(makeBuf< dht::Key_t >(0xFF))
, txKey(makeBuf< dht::Key_t >(0x01))
, txId(2)
, txOwner(txKey, txId)
, address(makeBuf< service::Address >(0x03))
, r(4)
{
EXPECT_CALL(context, OurKey()).WillOnce(ReturnRef(ourKey));
serviceAddressLookup = std::make_unique< dht::ServiceAddressLookup >(
txOwner, address, &context, r,
std::bind(&MockIntroSetHandler::call, &introsetHandler,
std::placeholders::_1));
}
};
TEST_F(TestDhtServiceAddressLookup, validate)
{
// Concerns
// - introset fails to verify
// - introset topic is not the target
// - happy path
{
service::IntroSet introset;
EXPECT_CALL(context, Crypto()).WillOnce(Return(&crypto));
EXPECT_CALL(context, Now()).WillOnce(Return(EXPIRY));
EXPECT_CALL(crypto, verify(_, _, _)).WillOnce(Return(false));
ASSERT_FALSE(serviceAddressLookup->Validate(introset));
}
{
service::IntroSet introset;
// Fiddle with the introset so we pass the Verify call
introset.I.emplace_back();
introset.I.front().expiresAt =
EXPIRY + service::MAX_INTROSET_TIME_DELTA + 1;
// Set expectations
EXPECT_CALL(context, Crypto()).WillOnce(Return(&crypto));
EXPECT_CALL(context, Now()).WillOnce(Return(EXPIRY));
EXPECT_CALL(crypto, verify(_, _, _)).WillOnce(Return(true));
ASSERT_FALSE(serviceAddressLookup->Validate(introset));
}
{
service::IntroSet introset;
// Set the current address of the lookup to be equal to the default address.
// This is easier than manipulating the ServiceInfo address.
serviceAddressLookup->target.Zero();
// Fiddle with the introset so we pass the Verify call
introset.I.emplace_back();
introset.I.front().expiresAt =
EXPIRY + service::MAX_INTROSET_TIME_DELTA + 1;
// Set expectations
EXPECT_CALL(context, Crypto()).WillOnce(Return(&crypto));
EXPECT_CALL(context, Now()).WillOnce(Return(EXPIRY));
EXPECT_CALL(crypto, verify(_, _, _)).WillOnce(Return(true));
ASSERT_TRUE(serviceAddressLookup->Validate(introset));
}
}
TEST_F(TestDhtServiceAddressLookup, start)
{
// Verify input arguments are passed correctly.
// The actual logic is inside the `dht::AbstractContext` implementation.
// clang-format off
EXPECT_CALL(context, DHTSendTo(
Eq(txKey.as_array()),
WhenDynamicCastTo< dht::FindIntroMessage* >(NotNull()),
true)
).Times(1);
// clang-format off
ASSERT_NO_THROW(serviceAddressLookup->Start(txOwner));
}
TEST_F(TestDhtServiceAddressLookup, get_next_peer)
{
// Concerns
// - Nodes returns nullptr
// - Happy path
dht::Key_t key = makeBuf< dht::Key_t >(0x02);
std::set< dht::Key_t > exclude;
{
EXPECT_CALL(context, Nodes()).WillOnce(ReturnNull());
ASSERT_FALSE(serviceAddressLookup->GetNextPeer(key, exclude));
}
{
uint64_t randVal = 0;
dht::Bucket< dht::RCNode > nodes(ourKey, [&]() { return randVal++; });
nodes.nodes.emplace(makeBuf< dht::Key_t >(0x03), dht::RCNode());
EXPECT_CALL(context, Nodes()).WillOnce(Return(&nodes));
ASSERT_TRUE(serviceAddressLookup->GetNextPeer(key, exclude));
}
}
TEST_F(TestDhtServiceAddressLookup, do_next)
{
// Concerns:
// - R != 0
// - R = 0
const dht::Key_t key = makeBuf< dht::Key_t >(0x02);
{
// R != 0
EXPECT_CALL(context, LookupIntroSetRecursive(address, txKey, txId, key, r - 1, _));
ASSERT_NO_THROW(serviceAddressLookup->DoNextRequest(key));
}
{
// R = 0
serviceAddressLookup->R = 0;
EXPECT_CALL(context, LookupIntroSetIterative(address, txKey, txId, key, _));
ASSERT_NO_THROW(serviceAddressLookup->DoNextRequest(key));
}
}
TEST_F(TestDhtServiceAddressLookup, send_reply)
{
// Concerns
// - handle result is set, is called
// - handle result is not set
{
serviceAddressLookup->valuesFound.emplace_back();
EXPECT_CALL(introsetHandler, call(SizeIs(1)));
// clang-format off
EXPECT_CALL(
context,
DHTSendTo(
Eq(txKey.as_array()),
WhenDynamicCastTo<dht::GotIntroMessage *>(
AllOf(
NotNull(),
Field(&dht::GotIntroMessage::I, SizeIs(1))
)
),
true
)
);
// clang-format on
ASSERT_NO_THROW(serviceAddressLookup->SendReply());
}
{
serviceAddressLookup->valuesFound.clear();
serviceAddressLookup->valuesFound.emplace_back();
serviceAddressLookup->handleResult =
decltype(serviceAddressLookup->handleResult)();
// clang-format off
EXPECT_CALL(
context,
DHTSendTo(
Eq(txKey.as_array()),
WhenDynamicCastTo<dht::GotIntroMessage *>(
AllOf(
NotNull(),
Field(&dht::GotIntroMessage::I, SizeIs(1))
)
),
true
)
);
// clang-format on
ASSERT_NO_THROW(serviceAddressLookup->SendReply());
}
}

@ -0,0 +1,229 @@
#include <dht/taglookup.hpp>
#include <crypto/mock_crypto.hpp>
#include <dht/mock_context.hpp>
#include <dht/messages/gotintro.hpp>
#include <service/IntroSet.hpp>
#include <test_util.hpp>
#include <gtest/gtest.h>
using namespace llarp;
using namespace ::testing;
using test::makeBuf;
static constexpr uint64_t EXPIRY = 1548503831ull;
struct TestDhtTagLookup : public ::testing::Test
{
test::MockCrypto crypto;
dht::Key_t txKey;
uint64_t txId;
dht::TXOwner txOwner;
service::Tag tag;
test::MockContext context;
uint64_t r;
dht::TagLookup tagLookup;
TestDhtTagLookup()
: txKey(makeBuf< dht::Key_t >(0x01))
, txId(2)
, txOwner(txKey, txId)
, tag(makeBuf< service::Tag >(0x03))
, r(4)
, tagLookup(txOwner, tag, &context, r)
{
}
};
TEST_F(TestDhtTagLookup, validate)
{
// Concerns
// - introset fails to verify
// - introset topic is not the target
// - happy path
{
service::IntroSet introset;
EXPECT_CALL(context, Crypto()).WillOnce(Return(&crypto));
EXPECT_CALL(context, Now()).WillOnce(Return(EXPIRY));
EXPECT_CALL(crypto, verify(_, _, _)).WillOnce(Return(false));
ASSERT_FALSE(tagLookup.Validate(introset));
}
{
service::IntroSet introset;
// Set topic to be different to the current tag
introset.topic = makeBuf< service::Tag >(0x02);
// Fiddle with the introset so we pass the Verify call
introset.I.emplace_back();
introset.I.front().expiresAt =
EXPIRY + service::MAX_INTROSET_TIME_DELTA + 1;
// Set expectations
EXPECT_CALL(context, Crypto()).WillOnce(Return(&crypto));
EXPECT_CALL(context, Now()).WillOnce(Return(EXPIRY));
EXPECT_CALL(crypto, verify(_, _, _)).WillOnce(Return(true));
ASSERT_FALSE(tagLookup.Validate(introset));
}
{
service::IntroSet introset;
// Set topic to be equal to the current tag
introset.topic = tag;
// Fiddle with the introset so we pass the Verify call
introset.I.emplace_back();
introset.I.front().expiresAt =
EXPIRY + service::MAX_INTROSET_TIME_DELTA + 1;
// Set expectations
EXPECT_CALL(context, Crypto()).WillOnce(Return(&crypto));
EXPECT_CALL(context, Now()).WillOnce(Return(EXPIRY));
EXPECT_CALL(crypto, verify(_, _, _)).WillOnce(Return(true));
ASSERT_TRUE(tagLookup.Validate(introset));
}
}
TEST_F(TestDhtTagLookup, start)
{
// Verify input arguments are passed correctly.
// The actual logic is inside the `dht::AbstractContext` implementation.
// clang-format off
EXPECT_CALL(context, DHTSendTo(
Eq(txKey.as_array()),
WhenDynamicCastTo< dht::FindIntroMessage* >(NotNull()),
true)
).Times(1);
// clang-format off
ASSERT_NO_THROW(tagLookup.Start(txOwner));
}
TEST_F(TestDhtTagLookup, get_next_peer)
{
dht::Key_t key = makeBuf< dht::Key_t >(0x02);
std::set< dht::Key_t > exclude;
ASSERT_FALSE(tagLookup.GetNextPeer(key, exclude));
}
TEST_F(TestDhtTagLookup, do_next)
{
const dht::Key_t key = makeBuf< dht::Key_t >(0x02);
ASSERT_NO_THROW(tagLookup.DoNextRequest(key));
}
TEST_F(TestDhtTagLookup, send_reply)
{
// Concerns
// - empty values found
// - when found.size < 2
// - FindRandomIntroSetsWithTagExcluding returns empty
// - FindRandomIntroSetsWithTagExcluding result are added to call
// - DHTSendTo called with correct params
{
tagLookup.valuesFound.clear();
// clang-format off
EXPECT_CALL(context, FindRandomIntroSetsWithTagExcluding(tag, _, IsEmpty()))
.WillOnce(Return(std::set< service::IntroSet >()));
EXPECT_CALL(
context,
DHTSendTo(
Eq(txKey.as_array()),
WhenDynamicCastTo<dht::GotIntroMessage *>(
AllOf(
NotNull(),
Field(&dht::GotIntroMessage::I, IsEmpty())
)
),
true
)
);
// clang-format on
ASSERT_NO_THROW(tagLookup.SendReply());
}
{
tagLookup.valuesFound.clear();
std::set< service::IntroSet > results;
results.emplace();
// clang-format off
EXPECT_CALL(context, FindRandomIntroSetsWithTagExcluding(tag, _, IsEmpty()))
.WillOnce(Return(results));
EXPECT_CALL(
context,
DHTSendTo(
Eq(txKey.as_array()),
WhenDynamicCastTo<dht::GotIntroMessage *>(
AllOf(
NotNull(),
Field(&dht::GotIntroMessage::I, SizeIs(1))
)
),
true
)
);
// clang-format on
ASSERT_NO_THROW(tagLookup.SendReply());
}
{
// clang-format off
tagLookup.valuesFound.clear();
tagLookup.valuesFound.emplace_back();
EXPECT_CALL(context, FindRandomIntroSetsWithTagExcluding(tag, _, SizeIs(1)))
.WillOnce(Return(std::set< service::IntroSet >()));
EXPECT_CALL(
context,
DHTSendTo(
Eq(txKey.as_array()),
WhenDynamicCastTo<dht::GotIntroMessage *>(
AllOf(
NotNull(),
Field(&dht::GotIntroMessage::I, SizeIs(1))
)
),
true
)
);
// clang-format on
ASSERT_NO_THROW(tagLookup.SendReply());
}
{
tagLookup.valuesFound.clear();
tagLookup.valuesFound.emplace_back();
tagLookup.valuesFound.back().T = 1;
tagLookup.valuesFound.emplace_back();
tagLookup.valuesFound.back().T = 2;
// clang-format off
EXPECT_CALL(context, FindRandomIntroSetsWithTagExcluding(_, _, _)).Times(0);
EXPECT_CALL(
context,
DHTSendTo(
Eq(txKey.as_array()),
WhenDynamicCastTo<dht::GotIntroMessage *>(
AllOf(
NotNull(),
Field(&dht::GotIntroMessage::I, SizeIs(2))
)
),
true
)
);
// clang-format on
ASSERT_NO_THROW(tagLookup.SendReply());
}
}
Loading…
Cancel
Save