diff --git a/.circleci/config.yml b/.circleci/config.yml index 3319440a..9fd1c822 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,6 +68,7 @@ jobs: cmake git libconfig-dev + libgmock-dev libgtest-dev libopus-dev libsodium-dev diff --git a/.github/scripts/coverage-linux b/.github/scripts/coverage-linux index 4e73dae5..4b17fdf5 100755 --- a/.github/scripts/coverage-linux +++ b/.github/scripts/coverage-linux @@ -5,6 +5,7 @@ set -eu NPROC=$(nproc) sudo apt-get install -y --no-install-recommends \ + libgmock-dev \ libgtest-dev \ libopus-dev \ libsodium-dev \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b85350f..ba173ec3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -477,14 +477,25 @@ install_module(toxcore DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tox) # ################################################################################ +add_library(test_util STATIC + toxcore/DHT_test_util.cc + toxcore/DHT_test_util.hh + toxcore/crypto_core_test_util.cc + toxcore/crypto_core_test_util.hh + toxcore/network_test_util.cc + toxcore/network_test_util.hh + toxcore/test_util.cc + toxcore/test_util.hh) + function(unit_test subdir target) add_executable(unit_${target}_test ${subdir}/${target}_test.cc) + target_link_libraries(unit_${target}_test PRIVATE test_util) if(TARGET toxcore_static) target_link_libraries(unit_${target}_test PRIVATE toxcore_static) else() target_link_libraries(unit_${target}_test PRIVATE toxcore_shared) endif() - target_link_libraries(unit_${target}_test PRIVATE GTest::GTest GTest::Main) + target_link_libraries(unit_${target}_test PRIVATE GTest::gtest GTest::gtest_main GTest::gmock) set_target_properties(unit_${target}_test PROPERTIES COMPILE_FLAGS "${TEST_CXX_FLAGS}") add_test(NAME ${target} COMMAND ${CROSSCOMPILING_EMULATOR} unit_${target}_test) set_property(TEST ${target} PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw") @@ -504,6 +515,7 @@ if(GTEST_FOUND) unit_test(toxcore mem) unit_test(toxcore mono_time) unit_test(toxcore ping_array) + unit_test(toxcore test_util) unit_test(toxcore tox) unit_test(toxcore util) endif() diff --git a/other/analysis/gen-file.sh b/other/analysis/gen-file.sh index 623f8d28..10edd0b5 100644 --- a/other/analysis/gen-file.sh +++ b/other/analysis/gen-file.sh @@ -15,7 +15,7 @@ CPPFLAGS+=("-Itoxav") CPPFLAGS+=("-Itoxencryptsave") CPPFLAGS+=("-Ithird_party/cmp") -LDFLAGS=("-lopus" "-lsodium" "-lvpx" "-lpthread" "-lconfig" "-lgtest") +LDFLAGS=("-lopus" "-lsodium" "-lvpx" "-lpthread" "-lconfig" "-lgmock" "-lgtest") LDFLAGS+=("-fuse-ld=gold") LDFLAGS+=("-Wl,--detect-odr-violations") LDFLAGS+=("-Wl,--warn-common") diff --git a/other/analysis/run-clang b/other/analysis/run-clang index c1846407..f5ec50f4 100755 --- a/other/analysis/run-clang +++ b/other/analysis/run-clang @@ -10,7 +10,7 @@ run() { "${CPPFLAGS[@]}" \ "${LDFLAGS[@]}" \ "$@" \ - -std=c++11 \ + -std=c++17 \ -Werror \ -Weverything \ -Wno-alloca \ diff --git a/other/analysis/run-clang-analyze b/other/analysis/run-clang-analyze index 05e94e78..55cd0d91 100755 --- a/other/analysis/run-clang-analyze +++ b/other/analysis/run-clang-analyze @@ -9,7 +9,7 @@ run() { clang++ --analyze amalgamation.cc \ "${CPPFLAGS[@]}" \ "$@" \ - -std=c++11 + -std=c++20 } . other/analysis/variants.sh diff --git a/other/analysis/run-cppcheck b/other/analysis/run-cppcheck index 9dc6f179..7e341f69 100755 --- a/other/analysis/run-cppcheck +++ b/other/analysis/run-cppcheck @@ -34,6 +34,8 @@ CPPCHECK_CXX+=("--suppress=AssignmentAddressToInteger") CPPCHECK_CXX+=("--suppress=cstyleCast") # Used in Messenger.c for a static_assert(...) CPPCHECK_CXX+=("--suppress=sizeofFunctionCall") +# This is outdated. Range-for is a good choice. +CPPCHECK_CXX+=("--suppress=useStlAlgorithm") run() { echo "Running cppcheck in variant '$*'" diff --git a/other/analysis/run-gcc b/other/analysis/run-gcc index ce3338c5..8a49b325 100755 --- a/other/analysis/run-gcc +++ b/other/analysis/run-gcc @@ -11,7 +11,7 @@ run() { "${CPPFLAGS[@]}" \ "${LDFLAGS[@]}" \ "$@" \ - -std=c++11 \ + -std=c++17 \ -fdiagnostics-color=always \ -Wall \ -Wextra \ diff --git a/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 b/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 index 5502283b..be352b1f 100644 --- a/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 +++ b/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 @@ -1 +1 @@ -870f1e19aa3f3f802c7e53af3848df6a0f7af9ad4c98213aa1578fa325b30fad /usr/local/bin/tox-bootstrapd +bc830120a87517f830eb85494b769c523bd1696328938d46e9eac1eefea61d38 /usr/local/bin/tox-bootstrapd diff --git a/other/docker/circleci/Dockerfile b/other/docker/circleci/Dockerfile index 446e8477..3b23eed7 100644 --- a/other/docker/circleci/Dockerfile +++ b/other/docker/circleci/Dockerfile @@ -7,6 +7,7 @@ RUN apt-get update && \ clang \ cmake \ libconfig-dev \ + libgmock-dev \ libgtest-dev \ libopus-dev \ libsodium-dev \ diff --git a/other/docker/coverage/Dockerfile b/other/docker/coverage/Dockerfile index 42530fb7..7dcd635b 100644 --- a/other/docker/coverage/Dockerfile +++ b/other/docker/coverage/Dockerfile @@ -11,6 +11,7 @@ RUN apt-get update && \ git \ golang-1.18 \ libconfig-dev \ + libgmock-dev \ libgtest-dev \ libopus-dev \ libsodium-dev \ @@ -25,9 +26,11 @@ RUN apt-get update && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && pip3 install --no-cache-dir gcovr -# strip gtest so it doesn't end up in stack traces. This speeds up the +# strip gtest/gmock so it doesn't end up in stack traces. This speeds up the # mallocfail run below by a lot. RUN ["strip", "-g",\ + "/usr/lib/x86_64-linux-gnu/libgmock.a",\ + "/usr/lib/x86_64-linux-gnu/libgmock_main.a",\ "/usr/lib/x86_64-linux-gnu/libgtest.a",\ "/usr/lib/x86_64-linux-gnu/libgtest_main.a"] RUN ["curl", "-s", "https://codecov.io/bash", "-o", "/usr/local/bin/codecov"] diff --git a/other/docker/doxygen/Dockerfile b/other/docker/doxygen/Dockerfile index f79fd197..e10c645d 100644 --- a/other/docker/doxygen/Dockerfile +++ b/other/docker/doxygen/Dockerfile @@ -5,7 +5,7 @@ ENV LANG=en_US.UTF-8 \ LC_CTYPE=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 -RUN apk add --no-cache doxygen git graphviz \ +RUN apk add --no-cache doxygen git graphviz texlive \ && git clone --depth=1 https://github.com/jothepro/doxygen-awesome-css.git /work/doxygen-awesome-css WORKDIR /work COPY . /work/ diff --git a/sonar-project.properties b/sonar-project.properties index 4621a7de..d4be6b89 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,3 +10,6 @@ sonar.sources=. # Encoding of the source code. sonar.sourceEncoding=UTF-8 + +# More precise Python version. +sonar.python.version=3.11 diff --git a/toxcore/BUILD.bazel b/toxcore/BUILD.bazel index 56693417..8f8d34af 100644 --- a/toxcore/BUILD.bazel +++ b/toxcore/BUILD.bazel @@ -10,6 +10,23 @@ exports_files( visibility = ["//c-toxcore:__pkg__"], ) +cc_library( + name = "test_util", + srcs = ["test_util.cc"], + hdrs = ["test_util.hh"], +) + +cc_test( + name = "test_util_test", + size = "small", + srcs = ["test_util_test.cc"], + deps = [ + ":crypto_core_test_util", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "attributes", hdrs = ["attributes.h"], @@ -142,6 +159,16 @@ cc_library( ], ) +cc_library( + name = "crypto_core_test_util", + srcs = ["crypto_core_test_util.cc"], + hdrs = ["crypto_core_test_util.hh"], + deps = [ + ":crypto_core", + ":test_util", + ], +) + cc_test( name = "crypto_core_test", size = "small", @@ -149,6 +176,7 @@ cc_test( flaky = True, deps = [ ":crypto_core", + ":crypto_core_test_util", ":util", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", @@ -261,6 +289,16 @@ cc_library( ], ) +cc_library( + name = "network_test_util", + srcs = ["network_test_util.cc"], + hdrs = ["network_test_util.hh"], + deps = [ + ":crypto_core", + ":network", + ], +) + cc_test( name = "network_test", size = "small", @@ -357,13 +395,28 @@ cc_library( ], ) +cc_library( + name = "DHT_test_util", + srcs = ["DHT_test_util.cc"], + hdrs = ["DHT_test_util.hh"], + deps = [ + ":DHT", + ":crypto_core", + ":crypto_core_test_util", + ":network_test_util", + ":test_util", + ], +) + cc_test( name = "DHT_test", size = "small", srcs = ["DHT_test.cc"], deps = [ ":DHT", + ":DHT_test_util", ":crypto_core", + ":network_test_util", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], diff --git a/toxcore/DHT.c b/toxcore/DHT.c index 42b89215..d9a3fd5e 100644 --- a/toxcore/DHT.c +++ b/toxcore/DHT.c @@ -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); diff --git a/toxcore/DHT.h b/toxcore/DHT.h index bc58f8e4..14562670 100644 --- a/toxcore/DHT.h +++ b/toxcore/DHT.h @@ -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() diff --git a/toxcore/DHT_test.cc b/toxcore/DHT_test.cc index f5021613..5f957fe3 100644 --- a/toxcore/DHT_test.cc +++ b/toxcore/DHT_test.cc @@ -1,15 +1,26 @@ #include "DHT.h" +#include #include #include #include +#include +#include +#include "DHT_test_util.hh" #include "crypto_core.h" +#include "crypto_core_test_util.hh" +#include "network_test_util.hh" namespace { -using PublicKey = std::array; +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::PrintToString; +using ::testing::UnorderedElementsAre; + using SecretKey = std::array; struct KeyPair { @@ -19,72 +30,66 @@ struct KeyPair { explicit KeyPair(const Random *rng) { crypto_new_keypair(rng, pk.data(), sk.data()); } }; -template -std::array to_array(T const (&arr)[N]) +TEST(IdClosest, KeyIsClosestToItself) { - std::array stdarr; - std::copy(arr, arr + N, stdarr.begin()); - return stdarr; -} + Test_Random rng; -PublicKey random_pk(const Random *rng) -{ - PublicKey pk; - random_bytes(rng, pk.data(), pk.size()); - return pk; + 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) { - const Random *rng = system_random(); - ASSERT_NE(rng, nullptr); + Test_Random rng; 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) { - const Random *rng = system_random(); - ASSERT_NE(rng, nullptr); + Test_Random rng; - for (uint32_t i = 0; i < 100; ++i) { - PublicKey pk0 = random_pk(rng); - PublicKey pk1 = random_pk(rng); - PublicKey pk2 = random_pk(rng); + PublicKey pk0 = random_pk(rng); + PublicKey pk1 = random_pk(rng); + PublicKey pk2 = random_pk(rng); - ASSERT_NE(pk1, pk2); // RNG can't produce the same random key twice + ASSERT_NE(pk1, pk2); // RNG can't produce the same random key twice - // Two non-equal keys can't have the same distance from any given key. - EXPECT_NE(id_closest(pk0.data(), pk1.data(), pk2.data()), 0); + // Two non-equal keys can't have the same distance from any given key. + EXPECT_NE(id_closest(pk0.data(), pk1.data(), pk2.data()), 0); - if (id_closest(pk0.data(), pk1.data(), pk2.data()) == 1) { - EXPECT_EQ(id_closest(pk0.data(), pk2.data(), pk1.data()), 2); - } + if (id_closest(pk0.data(), pk1.data(), pk2.data()) == 1) { + EXPECT_EQ(id_closest(pk0.data(), pk2.data(), pk1.data()), 2); + } - if (id_closest(pk0.data(), pk1.data(), pk2.data()) == 2) { - EXPECT_EQ(id_closest(pk0.data(), pk2.data(), pk1.data()), 1); - } + if (id_closest(pk0.data(), pk1.data(), pk2.data()) == 2) { + EXPECT_EQ(id_closest(pk0.data(), pk2.data(), pk1.data()), 1); } } 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; @@ -94,14 +99,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 nodes{}; @@ -128,10 +133,143 @@ 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) +{ + Test_Random rng; + + // 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 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::mt19937 mt_rng; + + // Adding one that's closer will happen. + std::shuffle(nodes.begin(), nodes.end(), mt_rng); + 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(), mt_rng); + 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(), mt_rng); + 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) +{ + Test_Random rng; + + // 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_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(); - ASSERT_NE(rng, nullptr); + Test_Random rng; // Peers. const KeyPair sender(rng); @@ -187,7 +325,7 @@ TEST(Request, CreateAndParse) TEST(AnnounceNodes, SetAndTest) { - const Random *rng = system_random(); + Test_Random rng; const Network *ns = system_network(); const Memory *mem = system_memory(); @@ -202,14 +340,14 @@ TEST(AnnounceNodes, SetAndTest) uint8_t pk_data[CRYPTO_PUBLIC_KEY_SIZE]; memcpy(pk_data, dht_get_self_public_key(dht), sizeof(pk_data)); - PublicKey self_pk = to_array(pk_data); + PublicKey self_pk(to_array(pk_data)); PublicKey pk1 = random_pk(rng); ASSERT_NE(pk1, self_pk); // Test with maximally close key to self pk_data[CRYPTO_PUBLIC_KEY_SIZE - 1] = ~pk_data[CRYPTO_PUBLIC_KEY_SIZE - 1]; - PublicKey pk2 = to_array(pk_data); + PublicKey pk2(to_array(pk_data)); ASSERT_NE(pk2, pk1); IP_Port ip_port = {0}; diff --git a/toxcore/DHT_test_util.cc b/toxcore/DHT_test_util.cc new file mode 100644 index 00000000..1ed09e1e --- /dev/null +++ b/toxcore/DHT_test_util.cc @@ -0,0 +1,28 @@ +#include "DHT_test_util.hh" + +#include +#include + +#include "crypto_core_test_util.hh" +#include "network_test_util.hh" + +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; +} + +bool operator==(Node_format const &a, Node_format const &b) +{ + return std::memcmp(&a, &b, sizeof(Node_format)) == 0; +} + +std::ostream &operator<<(std::ostream &out, Node_format const &v) +{ + return out << "\n Node_format{\n" + << " public_key = " << PublicKey(v.public_key) << ",\n" + << " ip_port = " << v.ip_port << " }"; +} diff --git a/toxcore/DHT_test_util.hh b/toxcore/DHT_test_util.hh new file mode 100644 index 00000000..6f00d49a --- /dev/null +++ b/toxcore/DHT_test_util.hh @@ -0,0 +1,14 @@ +#ifndef C_TOXCORE_TOXCORE_DHT_TEST_UTIL_H +#define C_TOXCORE_TOXCORE_DHT_TEST_UTIL_H + +#include + +#include "DHT.h" + +bool operator==(Node_format const &a, Node_format const &b); + +std::ostream &operator<<(std::ostream &out, Node_format const &v); + +Node_format random_node_format(const Random *rng); + +#endif // C_TOXCORE_TOXCORE_DHT_TEST_UTIL_H diff --git a/toxcore/crypto_core_test.cc b/toxcore/crypto_core_test.cc index c3c8a6f9..19802275 100644 --- a/toxcore/crypto_core_test.cc +++ b/toxcore/crypto_core_test.cc @@ -6,13 +6,13 @@ #include #include +#include "crypto_core_test_util.hh" #include "util.h" namespace { using HmacKey = std::array; using Hmac = std::array; -using PublicKey = std::array; using SecretKey = std::array; using ExtPublicKey = std::array; using ExtSecretKey = std::array; @@ -21,8 +21,7 @@ using Nonce = std::array; TEST(CryptoCore, EncryptLargeData) { - const Random *rng = system_random(); - ASSERT_NE(rng, nullptr); + Test_Random rng; Nonce nonce{}; PublicKey pk; @@ -71,8 +70,7 @@ TEST(CryptoCore, IncrementNonceNumber) TEST(CryptoCore, Signatures) { - const Random *rng = system_random(); - ASSERT_NE(rng, nullptr); + Test_Random rng; ExtPublicKey pk; ExtSecretKey sk; @@ -96,8 +94,7 @@ TEST(CryptoCore, Signatures) TEST(CryptoCore, Hmac) { - const Random *rng = system_random(); - ASSERT_NE(rng, nullptr); + Test_Random rng; HmacKey sk; new_hmac_key(rng, sk.data()); diff --git a/toxcore/crypto_core_test_util.cc b/toxcore/crypto_core_test_util.cc new file mode 100644 index 00000000..61dba5b3 --- /dev/null +++ b/toxcore/crypto_core_test_util.cc @@ -0,0 +1,46 @@ +#include "crypto_core_test_util.hh" + +#include +#include + +PublicKey random_pk(const Random *rng) +{ + PublicKey pk; + random_bytes(rng, pk.data(), pk.size()); + return pk; +} + +std::ostream &operator<<(std::ostream &out, PublicKey const &pk) +{ + out << '"'; + for (uint8_t byte : pk) { + out << std::setw(2) << std::setfill('0') << std::hex << uint32_t(byte); + } + out << '"'; + return out; +} + +static void test_random_bytes(void *obj, uint8_t *bytes, size_t length) +{ + Test_Random *self = static_cast(obj); + std::generate(bytes, &bytes[length], std::ref(self->lcg)); +} + +static uint32_t test_random_uniform(void *obj, uint32_t upper_bound) +{ + Test_Random *self = static_cast(obj); + std::uniform_int_distribution distrib(0, upper_bound); + return distrib(self->lcg); +} + +Random_Funcs const Test_Random::vtable = { + test_random_bytes, + test_random_uniform, +}; + +Test_Random::Test_Random() + : self{&vtable, this} +{ +} + +Test_Random::operator Random const *() const { return &self; } diff --git a/toxcore/crypto_core_test_util.hh b/toxcore/crypto_core_test_util.hh new file mode 100644 index 00000000..d3939749 --- /dev/null +++ b/toxcore/crypto_core_test_util.hh @@ -0,0 +1,75 @@ +#ifndef C_TOXCORE_TOXCORE_CRYPTO_CORE_TEST_UTIL_H +#define C_TOXCORE_TOXCORE_CRYPTO_CORE_TEST_UTIL_H + +#include +#include +#include +#include + +#include "crypto_core.h" +#include "test_util.hh" + +/** + * A very simple, fast, and deterministic PRNG just for testing. + * + * We generally don't want to use system_random(), since it's a + * cryptographically secure PRNG and we don't need that in unit tests. + */ +class Test_Random { + static Random_Funcs const vtable; + Random const self; + +public: + Test_Random(); + operator Random const *() const; + + std::minstd_rand lcg; +}; + +struct PublicKey : private std::array { + using Base = std::array; + + using Base::begin; + using Base::data; + using Base::end; + using Base::size; + using Base::operator[]; + + PublicKey() = default; + explicit PublicKey(uint8_t const (&arr)[CRYPTO_PUBLIC_KEY_SIZE]) + : PublicKey(to_array(arr)) + { + } + explicit PublicKey(std::array const &arr) + { + std::copy(arr.begin(), arr.end(), begin()); + } + + PublicKey(std::initializer_list const &arr) + { + std::copy(arr.begin(), arr.end(), begin()); + } + + Base const &base() const { return *this; } +}; + +inline bool operator!=(PublicKey const &pk1, PublicKey const &pk2) +{ + return pk1.base() != pk2.base(); +} + +inline bool operator==(PublicKey const &pk1, PublicKey const &pk2) +{ + return pk1.base() == pk2.base(); +} + +inline bool operator==(PublicKey::Base const &pk1, PublicKey const &pk2) +{ + return pk1 == pk2.base(); +} + +std::ostream &operator<<(std::ostream &out, PublicKey const &pk); + +PublicKey random_pk(const Random *rng); + +#endif // C_TOXCORE_TOXCORE_CRYPTO_CORE_TEST_UTIL_H diff --git a/toxcore/network_test_util.cc b/toxcore/network_test_util.cc new file mode 100644 index 00000000..daee5e3b --- /dev/null +++ b/toxcore/network_test_util.cc @@ -0,0 +1,42 @@ +#include "network_test_util.hh" + +#include + +IP_Port increasing_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_); + ++start_; + return ip_port; +} + +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; +} + +std::ostream &operator<<(std::ostream &out, IP const &v) +{ + Ip_Ntoa ip_str; + out << '"' << net_ip_ntoa(&v, &ip_str) << '"'; + return out; +} + +std::ostream &operator<<(std::ostream &out, IP_Port const &v) +{ + return out << "IP_Port{\n" + << " ip = " << v.ip << ",\n" + << " port = " << std::dec << std::setw(0) << v.port << " }"; +} diff --git a/toxcore/network_test_util.hh b/toxcore/network_test_util.hh new file mode 100644 index 00000000..4e55a52a --- /dev/null +++ b/toxcore/network_test_util.hh @@ -0,0 +1,28 @@ +#ifndef C_TOXCORE_TOXCORE_NETWORK_TEST_UTIL_H +#define C_TOXCORE_TOXCORE_NETWORK_TEST_UTIL_H + +#include + +#include "crypto_core.h" +#include "network.h" + +IP_Port random_ip_port(const Random *rng); + +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) + { + } + + IP_Port operator()(); +}; + +std::ostream &operator<<(std::ostream &out, IP const &v); +std::ostream &operator<<(std::ostream &out, IP_Port const &v); + +#endif // C_TOXCORE_TOXCORE_NETWORK_TEST_UTIL_H diff --git a/toxcore/test_util.cc b/toxcore/test_util.cc new file mode 100644 index 00000000..5566ced4 --- /dev/null +++ b/toxcore/test_util.cc @@ -0,0 +1 @@ +#include "test_util.hh" diff --git a/toxcore/test_util.hh b/toxcore/test_util.hh new file mode 100644 index 00000000..c5eb6d9c --- /dev/null +++ b/toxcore/test_util.hh @@ -0,0 +1,43 @@ +#ifndef C_TOXCORE_TOXCORE_TEST_UTIL_H +#define C_TOXCORE_TOXCORE_TEST_UTIL_H + +#include +#include +#include + +template +std::array to_array(T const (&arr)[N]) +{ + std::array stdarr; + std::copy(arr, arr + N, stdarr.begin()); + return stdarr; +} + +template +auto array_of(T &&make, Args... args) +{ + std::array::type, N> arr; + for (auto &elem : arr) { + elem = make(args...); + } + return arr; +} + +template +auto vector_of(std::size_t n, T &&make, Args... args) +{ + std::vector::type> vec; + for (std::size_t i = 0; i < n; ++i) { + vec.push_back(make(args...)); + } + return vec; +} + +template +Container sorted(Container arr, Less less) +{ + std::sort(arr.begin(), arr.end(), less); + return arr; +} + +#endif // C_TOXCORE_TOXCORE_TEST_UTIL_H diff --git a/toxcore/test_util_test.cc b/toxcore/test_util_test.cc new file mode 100644 index 00000000..2ef2e944 --- /dev/null +++ b/toxcore/test_util_test.cc @@ -0,0 +1,64 @@ +#include "test_util.hh" + +#include +#include + +#include + +#include "crypto_core_test_util.hh" + +namespace { + +using ::testing::Each; +using ::testing::Eq; + +TEST(CryptoCoreTestUtil, RandomBytesDoesNotTouchZeroSizeArray) +{ + const Test_Random rng; + std::array bytes{}; + for (uint32_t i = 0; i < 100; ++i) { + random_bytes(rng, bytes.data(), 0); + ASSERT_THAT(bytes, Each(Eq(0x00))); + } +} + +TEST(CryptoCoreTestUtil, RandomBytesFillsEntireArray) +{ + const Test_Random rng; + std::array bytes{}; + + for (uint32_t size = 1; size < bytes.size(); ++size) { + bool const success = [&]() { + // Try a few times. There ought to be a non-zero byte in our randomness at + // some point. + for (uint32_t i = 0; i < 100; ++i) { + random_bytes(rng, bytes.data(), bytes.size()); + if (bytes[size - 1] != 0x00) { + return true; + } + } + return false; + }(); + ASSERT_TRUE(success); + } +} + +TEST(CryptoCoreTestUtil, RandomBytesDoesNotBufferOverrun) +{ + const Test_Random rng; + + std::array bytes{}; + + // Try a few times. It should never overrun. + for (uint32_t i = 0; i < 100; ++i) { + for (uint32_t diff = 1; diff < sizeof(uint64_t); ++diff) { + bytes = {}; + random_bytes(rng, bytes.data(), bytes.size() - diff); + // All bytes not in the range we want to write should be 0. + ASSERT_THAT(std::vector(bytes.begin() + (bytes.size() - diff), bytes.end()), + Each(Eq(0x00))); + } + } +} + +} // namespace