Skip to content

Commit

Permalink
test: Add more unit tests for add_to_list.
Browse files Browse the repository at this point in the history
  • Loading branch information
iphydf committed Jan 9, 2024
1 parent 05ce5c1 commit ea7570b
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 36 deletions.
2 changes: 1 addition & 1 deletion other/bootstrap_daemon/docker/tox-bootstrapd.sha256
Original file line number Diff line number Diff line change
@@ -1 +1 @@
870f1e19aa3f3f802c7e53af3848df6a0f7af9ad4c98213aa1578fa325b30fad /usr/local/bin/tox-bootstrapd
bc830120a87517f830eb85494b769c523bd1696328938d46e9eac1eefea61d38 /usr/local/bin/tox-bootstrapd
11 changes: 11 additions & 0 deletions toxcore/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,23 @@ cc_library(
],
)

cc_library(
name = "DHT_test_util",
hdrs = ["DHT_test_util.h"],
srcs = ["DHT_test_util.cc"],
deps = [
":DHT",
":crypto_core",
],
)

cc_test(
name = "DHT_test",
size = "small",
srcs = ["DHT_test.cc"],
deps = [
":DHT",
":DHT_test_util",
":crypto_core",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
Expand Down
19 changes: 12 additions & 7 deletions toxcore/DHT.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,16 +750,21 @@ static bool client_or_ip_port_in_list(const Logger *log, const Mono_Time *mono_t
return true;
}

bool add_to_list(Node_format *nodes_list, uint32_t length, const uint8_t *pk, const IP_Port *ip_port,
const uint8_t *cmp_pk)
bool add_to_list(
Node_format *nodes_list, uint32_t length, const uint8_t pk[CRYPTO_PUBLIC_KEY_SIZE],
const IP_Port *ip_port, const uint8_t cmp_pk[CRYPTO_PUBLIC_KEY_SIZE])
{
for (uint32_t i = 0; i < length; ++i) {
if (id_closest(cmp_pk, nodes_list[i].public_key, pk) == 2) {
Node_format *node = &nodes_list[i];

if (id_closest(cmp_pk, node->public_key, pk) == 2) {
uint8_t pk_bak[CRYPTO_PUBLIC_KEY_SIZE];
memcpy(pk_bak, nodes_list[i].public_key, CRYPTO_PUBLIC_KEY_SIZE);
const IP_Port ip_port_bak = nodes_list[i].ip_port;
memcpy(nodes_list[i].public_key, pk, CRYPTO_PUBLIC_KEY_SIZE);
nodes_list[i].ip_port = *ip_port;
memcpy(pk_bak, node->public_key, CRYPTO_PUBLIC_KEY_SIZE);

const IP_Port ip_port_bak = node->ip_port;
memcpy(node->public_key, pk, CRYPTO_PUBLIC_KEY_SIZE);

node->ip_port = *ip_port;

if (i != length - 1) {
add_to_list(nodes_list, length, pk_bak, &ip_port_bak, cmp_pk);
Expand Down
3 changes: 2 additions & 1 deletion toxcore/DHT.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,8 @@ unsigned int bit_by_bit_cmp(const uint8_t *pk1, const uint8_t *pk2);
*/
non_null()
bool add_to_list(
Node_format *nodes_list, uint32_t length, const uint8_t *pk, const IP_Port *ip_port, const uint8_t *cmp_pk);
Node_format *nodes_list, uint32_t length, const uint8_t pk[CRYPTO_PUBLIC_KEY_SIZE],
const IP_Port *ip_port, const uint8_t cmp_pk[CRYPTO_PUBLIC_KEY_SIZE]);

/** Return 1 if node can be added to close list, 0 if it can't. */
non_null()
Expand Down
198 changes: 171 additions & 27 deletions toxcore/DHT_test.cc
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
#include "DHT.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <algorithm>
#include <array>
#include <cstring>
#include <random>

#include "DHT_test_util.h"
#include "crypto_core.h"

namespace {

using PublicKey = std::array<uint8_t, CRYPTO_PUBLIC_KEY_SIZE>;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::PrintToString;
using ::testing::UnorderedElementsAre;

using SecretKey = std::array<uint8_t, CRYPTO_SECRET_KEY_SIZE>;

struct KeyPair {
Expand All @@ -19,19 +28,43 @@ struct KeyPair {
explicit KeyPair(const Random *rng) { crypto_new_keypair(rng, pk.data(), sk.data()); }
};

template <typename T, size_t N>
std::array<T, N> to_array(T const (&arr)[N])
{
std::array<T, N> stdarr;
std::copy(arr, arr + N, stdarr.begin());
return stdarr;
}
class increasing_ip_port {
uint8_t start_;
const Random *rng_;

public:
explicit increasing_ip_port(uint8_t start, const Random *rng)
: start_(start)
, rng_(rng)
{
}

PublicKey random_pk(const Random *rng)
IP_Port operator()()
{
IP_Port ip_port;
ip_port.ip.family = net_family_ipv4();
ip_port.ip.ip.v4.uint8[0] = 192;
ip_port.ip.ip.v4.uint8[1] = 168;
ip_port.ip.ip.v4.uint8[2] = 0;
ip_port.ip.ip.v4.uint8[3] = start_++;
ip_port.port = random_u16(rng_);
return ip_port;
}
};

TEST(IdClosest, KeyIsClosestToItself)
{
PublicKey pk;
random_bytes(rng, pk.data(), pk.size());
return pk;
const Random *rng = system_random();
ASSERT_NE(rng, nullptr);

PublicKey pk0 = random_pk(rng);
PublicKey pk1;
do {
// Get a random key that's not the same as pk0.
pk1 = random_pk(rng);
} while (pk0 == pk1);

EXPECT_EQ(id_closest(pk0.data(), pk0.data(), pk1.data()), 1);
}

TEST(IdClosest, IdenticalKeysAreSameDistance)
Expand All @@ -41,9 +74,8 @@ TEST(IdClosest, IdenticalKeysAreSameDistance)

PublicKey pk0 = random_pk(rng);
PublicKey pk1 = random_pk(rng);
PublicKey pk2 = pk1;

EXPECT_EQ(id_closest(pk0.data(), pk1.data(), pk2.data()), 0);
EXPECT_EQ(id_closest(pk0.data(), pk1.data(), pk1.data()), 0);
}

TEST(IdClosest, DistanceIsCommutative)
Expand Down Expand Up @@ -73,18 +105,18 @@ TEST(IdClosest, DistanceIsCommutative)

TEST(IdClosest, SmallXorDistanceIsCloser)
{
PublicKey const pk0 = {{0xaa}};
PublicKey const pk1 = {{0xa0}};
PublicKey const pk2 = {{0x0a}};
PublicKey const pk0 = {0xaa};
PublicKey const pk1 = {0xa0};
PublicKey const pk2 = {0x0a};

EXPECT_EQ(id_closest(pk0.data(), pk1.data(), pk2.data()), 1);
}

TEST(IdClosest, DistinctKeysCannotHaveTheSameDistance)
{
PublicKey const pk0 = {{0x06}};
PublicKey const pk1 = {{0x00}};
PublicKey pk2 = {{0x00}};
PublicKey const pk0 = {0x06};
PublicKey const pk1 = {0x00};
PublicKey pk2 = {0x00};

for (uint8_t i = 1; i < 0xff; ++i) {
pk2[0] = i;
Expand All @@ -94,14 +126,14 @@ TEST(IdClosest, DistinctKeysCannotHaveTheSameDistance)

TEST(AddToList, OverridesKeysWithCloserKeys)
{
PublicKey const self_pk = {{0xaa}};
PublicKey const self_pk = {0xaa};
PublicKey const keys[] = {
{{0xa0}}, // closest
{{0x0a}}, //
{{0x0b}}, //
{{0x0c}}, //
{{0x0d}}, //
{{0xa1}}, // closer than the 4 keys above
{0xa0}, // closest
{0x0a}, //
{0x0b}, //
{0x0c}, //
{0x0d}, //
{0xa1}, // closer than the 4 keys above
};

std::array<Node_format, 4> nodes{};
Expand All @@ -128,6 +160,118 @@ TEST(AddToList, OverridesKeysWithCloserKeys)
EXPECT_EQ(to_array(nodes[3].public_key), keys[2]);
}

Node_format fill(Node_format v, PublicKey const &pk, IP_Port const &ip_port)
{
std::copy(pk.begin(), pk.end(), v.public_key);
v.ip_port = ip_port;
return v;
}

TEST(AddToList, AddsFirstKeysInOrder)
{
const Random *rng = system_random();
ASSERT_NE(rng, nullptr);

// Make cmp_key the furthest away from 00000... as possible, so all initial inserts succeed.
PublicKey const cmp_pk{0xff, 0xff, 0xff, 0xff};

// Generate a bunch of other keys, sorted by distance from cmp_pk.
auto const keys
= sorted(array_of<20>(random_pk, rng), [&cmp_pk](auto const &pk1, auto const &pk2) {
return id_closest(cmp_pk.data(), pk1.data(), pk2.data()) == 1;
});
auto const ips = array_of<20>(increasing_ip_port(0, rng));

std::vector<Node_format> nodes(4);

// Add a bunch of nodes.
ASSERT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[2].data(), &ips[2], cmp_pk.data()))
<< "failed to insert\n cmp_pk = " << cmp_pk << "\n pk = " << keys[2]
<< "\n nodes_list = " << PrintToString(nodes);
ASSERT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[5].data(), &ips[5], cmp_pk.data()))
<< "failed to insert\n cmp_pk = " << cmp_pk << "\n pk = " << keys[5]
<< "\n nodes_list = " << PrintToString(nodes);
ASSERT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[7].data(), &ips[7], cmp_pk.data()))
<< "failed to insert\n cmp_pk = " << cmp_pk << "\n pk = " << keys[7]
<< "\n nodes_list = " << PrintToString(nodes);
ASSERT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[9].data(), &ips[9], cmp_pk.data()))
<< "failed to insert\n cmp_pk = " << cmp_pk << "\n pk = " << keys[9]
<< "\n nodes_list = " << PrintToString(nodes);

// They should all appear in order.
EXPECT_THAT(nodes,
ElementsAre(fill(Node_format{}, keys[2], ips[2]), fill(Node_format{}, keys[5], ips[5]),
fill(Node_format{}, keys[7], ips[7]), fill(Node_format{}, keys[9], ips[9])));

// Adding another node that's further away will not happen.
ASSERT_FALSE(add_to_list(nodes.data(), nodes.size(), keys[10].data(), &ips[10], cmp_pk.data()))
<< "incorrectly inserted\n cmp_pk = " << cmp_pk << "\n pk = " << keys[10]
<< "\n nodes_list = " << PrintToString(nodes);

// Now shuffle each time we add a node, which should work fine.
std::random_device rd;
std::mt19937 g(rd());

// Adding one that's closer will happen.
std::shuffle(nodes.begin(), nodes.end(), g);
ASSERT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[8].data(), &ips[8], cmp_pk.data()))
<< "failed to insert\n cmp_pk = " << cmp_pk << "\n pk = " << keys[8]
<< "\n nodes_list = " << PrintToString(nodes);

EXPECT_THAT(nodes,
UnorderedElementsAre(fill(Node_format{}, keys[2], ips[2]),
fill(Node_format{}, keys[5], ips[5]), fill(Node_format{}, keys[7], ips[7]),
fill(Node_format{}, keys[8], ips[8])));

// Adding one that's closer than almost all of them will happen.
std::shuffle(nodes.begin(), nodes.end(), g);
ASSERT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[4].data(), &ips[4], cmp_pk.data()))
<< "failed to insert\n cmp_pk = " << cmp_pk << "\n pk = " << keys[4]
<< "\n nodes_list = " << PrintToString(nodes);

EXPECT_THAT(nodes,
UnorderedElementsAre(fill(Node_format{}, keys[2], ips[2]),
fill(Node_format{}, keys[4], ips[4]), fill(Node_format{}, keys[5], ips[5]),
fill(Node_format{}, keys[7], ips[7])));

// Adding one that's closer than all of them will happen.
std::shuffle(nodes.begin(), nodes.end(), g);
ASSERT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[1].data(), &ips[1], cmp_pk.data()))
<< "failed to insert\n cmp_pk = " << cmp_pk << "\n pk = " << keys[1]
<< "\n nodes_list = " << PrintToString(nodes);

EXPECT_THAT(nodes,
UnorderedElementsAre(fill(Node_format{}, keys[1], ips[1]),
fill(Node_format{}, keys[2], ips[2]), fill(Node_format{}, keys[4], ips[4]),
fill(Node_format{}, keys[5], ips[5])));
}

TEST(AddToList, KeepsKeysInOrder)
{
const Random *rng = system_random();
ASSERT_NE(rng, nullptr);

// Any random cmp_pk should work, as well as the smallest or (approximately) largest pk.
for (PublicKey const cmp_pk : {random_pk(rng), PublicKey{0x00}, PublicKey{0xff, 0xff}}) {
auto const by_distance = [&cmp_pk](auto const &node1, auto const &node2) {
return id_closest(cmp_pk.data(), node1.public_key, node2.public_key) == 1;
};

// Generate a bunch of other keys, not sorted.
auto const nodes = vector_of(16, random_node_format, rng);

std::vector<Node_format> node_list(4);

// Add all of them.
for (Node_format const &node : nodes) {
add_to_list(
node_list.data(), node_list.size(), node.public_key, &node.ip_port, cmp_pk.data());
// Nodes should always be sorted.
EXPECT_THAT(node_list, Eq(sorted(node_list, by_distance)));
}
}
}

TEST(Request, CreateAndParse)
{
const Random *rng = system_random();
Expand Down
29 changes: 29 additions & 0 deletions toxcore/DHT_test_util.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "DHT_test_util.h"

PublicKey random_pk(const Random *rng)
{
PublicKey pk;
random_bytes(rng, pk.data(), pk.size());
return pk;
}

IP_Port random_ip_port(const Random *rng)
{
IP_Port ip_port;
ip_port.ip.family = net_family_ipv4();
ip_port.ip.ip.v4.uint8[0] = 192;
ip_port.ip.ip.v4.uint8[1] = 168;
ip_port.ip.ip.v4.uint8[2] = 0;
ip_port.ip.ip.v4.uint8[3] = random_u08(rng);
ip_port.port = random_u16(rng);
return ip_port;
}

Node_format random_node_format(const Random *rng)
{
Node_format node;
auto const pk = random_pk(rng);
std::copy(pk.begin(), pk.end(), node.public_key);
node.ip_port = random_ip_port(rng);
return node;
}
Loading

0 comments on commit ea7570b

Please sign in to comment.