forked from Green-Sky/tomato
Squashed 'external/toxcore/c-toxcore/' changes from 1828c5356..c9cdae001
c9cdae001 fix(toxav): remove extra copy of video frame on encode 4f6d4546b test: Improve the fake network library. a2581e700 refactor(toxcore): generate `Friend_Request` and `Dht_Nodes_Response` 2aaa11770 refactor(toxcore): use Tox_Memory in generated events 5c367452b test(toxcore): fix incorrect mutex in tox_scenario_get_time 8f92e710f perf: Add a timed limit of number of cookie requests. 695b6417a test: Add some more simulated network support. 815ae9ce9 test(toxcore): fix thread-safety in scenario framework 6d85c754e test(toxcore): add unit tests for net_crypto 9c22e79cc test(support): add SimulatedEnvironment for deterministic testing f34fcb195 chore: Update windows Dockerfile to debian stable (trixie). ece0e8980 fix(group_moderation): allow validating unsorted sanction list signatures a4fa754d7 refactor: rename struct Packet to struct Net_Packet d6f330f85 cleanup: Fix some warnings from coverity. e206bffa2 fix(group_chats): fix sync packets reverting topics 0e4715598 test: Add new scenario testing framework. 668291f44 refactor(toxcore): decouple Network_Funcs from sockaddr via IP_Port fc4396cef fix: potential division by zero in toxav and unsafe hex parsing 8e8b352ab refactor: Add nullable annotations to struct members. 7740bb421 refactor: decouple net_crypto from DHT 1936d4296 test: add benchmark for toxav audio and video 46bfdc2df fix: correct printf format specifiers for unsigned integers REVERT: 1828c5356 fix(toxav): remove extra copy of video frame on encode git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: c9cdae001341e701fca980c9bb9febfeb95d2902
This commit is contained in:
@@ -15,6 +15,7 @@ sh_test(
|
||||
"-Wno-boolean-return",
|
||||
"-Wno-callback-names",
|
||||
"-Wno-enum-from-int",
|
||||
"-Wno-tagged-union",
|
||||
"+RTS",
|
||||
"-N4",
|
||||
"-RTS",
|
||||
|
||||
@@ -38,3 +38,5 @@ if(BUILD_MISC_TESTS)
|
||||
target_link_libraries(Messenger_test PRIVATE Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(support)
|
||||
|
||||
@@ -108,7 +108,7 @@ int main(int argc, char *argv[])
|
||||
m = new_messenger(mono_time, mem, os_random(), os_network(), &options, &err);
|
||||
|
||||
if (!m) {
|
||||
fprintf(stderr, "Failed to allocate messenger datastructure: %d\n", err);
|
||||
fprintf(stderr, "Failed to allocate messenger datastructure: %u\n", err);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,6 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary")
|
||||
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
|
||||
|
||||
cc_library(
|
||||
name = "fuzz_support",
|
||||
srcs = [
|
||||
"func_conversion.hh",
|
||||
"fuzz_support.cc",
|
||||
],
|
||||
hdrs = ["fuzz_support.hh"],
|
||||
visibility = ["//c-toxcore:__subpackages__"],
|
||||
deps = [
|
||||
"//c-toxcore/toxcore:crypto_core",
|
||||
"//c-toxcore/toxcore:network",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "fuzz_tox",
|
||||
hdrs = ["fuzz_tox.hh"],
|
||||
visibility = ["//c-toxcore:__subpackages__"],
|
||||
deps = [":fuzz_support"],
|
||||
)
|
||||
|
||||
cc_fuzz_test(
|
||||
name = "bootstrap_fuzz_test",
|
||||
size = "small",
|
||||
@@ -30,8 +8,7 @@ cc_fuzz_test(
|
||||
copts = ["-UNDEBUG"],
|
||||
corpus = ["//tools/toktok-fuzzer/corpus:bootstrap_fuzz_test"],
|
||||
deps = [
|
||||
":fuzz_support",
|
||||
":fuzz_tox",
|
||||
"//c-toxcore/testing/support",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
"//c-toxcore/toxcore:tox_dispatch",
|
||||
"//c-toxcore/toxcore:tox_events",
|
||||
@@ -46,8 +23,7 @@ cc_fuzz_test(
|
||||
corpus = ["//tools/toktok-fuzzer/corpus:e2e_fuzz_test"],
|
||||
data = ["//tools/toktok-fuzzer/init:e2e_fuzz_test.dat"],
|
||||
deps = [
|
||||
":fuzz_support",
|
||||
":fuzz_tox",
|
||||
"//c-toxcore/testing/support",
|
||||
"//c-toxcore/toxcore:crypto_core",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
"//c-toxcore/toxcore:tox_dispatch",
|
||||
@@ -62,7 +38,7 @@ cc_fuzz_test(
|
||||
copts = ["-UNDEBUG"],
|
||||
corpus = ["//tools/toktok-fuzzer/corpus:toxsave_fuzz_test"],
|
||||
deps = [
|
||||
":fuzz_support",
|
||||
"//c-toxcore/testing/support",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
],
|
||||
)
|
||||
@@ -72,7 +48,7 @@ cc_binary(
|
||||
srcs = ["protodump.cc"],
|
||||
copts = ["-UNDEBUG"],
|
||||
deps = [
|
||||
":fuzz_support",
|
||||
"//c-toxcore/testing/support",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
"//c-toxcore/toxcore:tox_dispatch",
|
||||
"//c-toxcore/toxcore:tox_events",
|
||||
@@ -90,38 +66,3 @@ genrule(
|
||||
tags = ["manual"],
|
||||
tools = [":protodump"],
|
||||
)
|
||||
|
||||
# bazel test --config=asan-libfuzzer //c-toxcore/testing/fuzzing:protodump_reduce_test
|
||||
cc_test(
|
||||
name = "protodump_reduce_test",
|
||||
size = "small",
|
||||
srcs = ["protodump_reduce.cc"],
|
||||
args = ["$(location :e2e_fuzz_test_init.dat)"],
|
||||
copts = ["-UNDEBUG"],
|
||||
data = [":e2e_fuzz_test_init.dat"],
|
||||
tags = ["manual"],
|
||||
deps = [
|
||||
":fuzz_support",
|
||||
":fuzz_tox",
|
||||
"//c-toxcore/toxcore:crypto_core",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
"//c-toxcore/toxcore:tox_dispatch",
|
||||
"//c-toxcore/toxcore:tox_events",
|
||||
"@rules_fuzzing//fuzzing:cc_engine",
|
||||
],
|
||||
)
|
||||
|
||||
cc_fuzz_test(
|
||||
name = "protodump_reduce",
|
||||
size = "small",
|
||||
srcs = ["protodump_reduce.cc"],
|
||||
copts = ["-UNDEBUG"],
|
||||
deps = [
|
||||
":fuzz_support",
|
||||
":fuzz_tox",
|
||||
"//c-toxcore/toxcore:crypto_core",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
"//c-toxcore/toxcore:tox_dispatch",
|
||||
"//c-toxcore/toxcore:tox_events",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Override network and random functions
|
||||
add_library(fuzz_support func_conversion.hh fuzz_support.cc fuzz_support.hh)
|
||||
|
||||
set(LIBFUZZER_LINKER_FLAGS)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
@@ -12,9 +11,9 @@ function(fuzz_test target source_dir)
|
||||
set(CORPUS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/toktok-fuzzer/corpus/${target}_fuzz_test)
|
||||
file(GLOB CORPUS "${CORPUS_DIR}/*")
|
||||
add_executable(${target}_fuzz_test ${source_dir}/${target}_fuzz_test.cc)
|
||||
target_link_libraries(${target}_fuzz_test PRIVATE fuzz_support test_util toxcore_fuzz ${LIBFUZZER_LINKER_FLAGS})
|
||||
target_link_libraries(${target}_fuzz_test PRIVATE support test_util toxcore_fuzz ${LIBFUZZER_LINKER_FLAGS})
|
||||
if(CORPUS)
|
||||
add_test(NAME ${target}_fuzz COMMAND ${CROSSCOMPILING_EMULATOR} ${target}_fuzz_test -max_total_time=10 ${CORPUS})
|
||||
add_test(NAME ${target}_fuzz COMMAND ${target}_fuzz_test -max_total_time=10 ${CORPUS})
|
||||
set_property(TEST ${target}_fuzz PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw;srcdir=${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "../../toxcore/tox.h"
|
||||
#include "../../toxcore/tox_dispatch.h"
|
||||
#include "../../toxcore/tox_events.h"
|
||||
#include "fuzz_support.hh"
|
||||
#include "fuzz_tox.hh"
|
||||
#include "../support/public/fuzz_data.hh"
|
||||
#include "../support/public/fuzz_helpers.hh"
|
||||
#include "../support/public/simulated_environment.hh"
|
||||
|
||||
namespace {
|
||||
|
||||
using tox::test::configure_fuzz_memory_source;
|
||||
using tox::test::configure_fuzz_packet_source;
|
||||
using tox::test::FakeClock;
|
||||
using tox::test::Fuzz_Data;
|
||||
using tox::test::SimulatedEnvironment;
|
||||
|
||||
template <typename T>
|
||||
using Ptr = std::unique_ptr<T, void (*)(T *)>;
|
||||
|
||||
void setup_callbacks(Tox_Dispatch *dispatch)
|
||||
{
|
||||
tox_events_callback_conference_connected(
|
||||
@@ -97,10 +109,15 @@ void setup_callbacks(Tox_Dispatch *dispatch)
|
||||
|
||||
void TestBootstrap(Fuzz_Data &input)
|
||||
{
|
||||
// Null system for regularly working memory allocations needed in
|
||||
// tox_events_equal.
|
||||
Null_System null_sys;
|
||||
Fuzz_System sys(input);
|
||||
SimulatedEnvironment env;
|
||||
env.fake_clock().advance(1000000000); // Match legacy behavior
|
||||
auto node = env.create_node(33445);
|
||||
configure_fuzz_memory_source(env.fake_memory(), input);
|
||||
configure_fuzz_packet_source(*node->endpoint, input);
|
||||
|
||||
// Create a second null system for tox_events_equal check
|
||||
SimulatedEnvironment null_env;
|
||||
auto null_node = null_env.create_node(0); // Port 0 (unbound/irrelevant)
|
||||
|
||||
Ptr<Tox_Options> opts(tox_options_new(nullptr), tox_options_free);
|
||||
assert(opts != nullptr);
|
||||
@@ -110,8 +127,26 @@ void TestBootstrap(Fuzz_Data &input)
|
||||
const char *message, void *user_data) {
|
||||
// Log to stdout.
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf("[tox1] %c %s:%u(%s): %s\n", tox_log_level_name(level), file, line,
|
||||
func, message);
|
||||
// Approximate level name mapping
|
||||
char level_char = '?';
|
||||
switch (level) {
|
||||
case TOX_LOG_LEVEL_TRACE:
|
||||
level_char = 'T';
|
||||
break;
|
||||
case TOX_LOG_LEVEL_DEBUG:
|
||||
level_char = 'D';
|
||||
break;
|
||||
case TOX_LOG_LEVEL_INFO:
|
||||
level_char = 'I';
|
||||
break;
|
||||
case TOX_LOG_LEVEL_WARNING:
|
||||
level_char = 'W';
|
||||
break;
|
||||
case TOX_LOG_LEVEL_ERROR:
|
||||
level_char = 'E';
|
||||
break;
|
||||
}
|
||||
std::printf("[tox1] %c %s:%u(%s): %s\n", level_char, file, line, func, message);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -134,7 +169,7 @@ void TestBootstrap(Fuzz_Data &input)
|
||||
}
|
||||
|
||||
Tox_Options_Testing tox_options_testing;
|
||||
tox_options_testing.operating_system = sys.sys.get();
|
||||
tox_options_testing.operating_system = &node->system;
|
||||
|
||||
Tox_Err_New error_new;
|
||||
Tox_Err_New_Testing error_new_testing;
|
||||
@@ -165,15 +200,17 @@ void TestBootstrap(Fuzz_Data &input)
|
||||
while (!input.empty()) {
|
||||
Tox_Err_Events_Iterate error_iterate;
|
||||
Tox_Events *events = tox_events_iterate(tox, true, &error_iterate);
|
||||
assert(tox_events_equal(null_sys.sys.get(), events, events));
|
||||
assert(tox_events_equal(&null_node->system, events, events));
|
||||
|
||||
tox_dispatch_invoke(dispatch, events, tox);
|
||||
tox_events_free(events);
|
||||
// Move the clock forward a decent amount so all the time-based checks
|
||||
// trigger more quickly.
|
||||
sys.clock += 200;
|
||||
|
||||
// If no input was consumed, something went wrong.
|
||||
assert(input_size != input.size());
|
||||
env.advance_time(200);
|
||||
|
||||
// If no input was consumed, stop.
|
||||
if (input_size == input.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
input_size = input.size();
|
||||
}
|
||||
|
||||
@@ -5,17 +5,29 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "../../toxcore/crypto_core.h"
|
||||
#include "../../toxcore/tox.h"
|
||||
#include "../../toxcore/tox_dispatch.h"
|
||||
#include "../../toxcore/tox_events.h"
|
||||
#include "fuzz_support.hh"
|
||||
#include "fuzz_tox.hh"
|
||||
#include "../support/public/fuzz_data.hh"
|
||||
#include "../support/public/fuzz_helpers.hh"
|
||||
#include "../support/public/simulated_environment.hh"
|
||||
|
||||
namespace {
|
||||
|
||||
using tox::test::configure_fuzz_memory_source;
|
||||
using tox::test::configure_fuzz_packet_source;
|
||||
using tox::test::configure_fuzz_random_source;
|
||||
using tox::test::FakeClock;
|
||||
using tox::test::Fuzz_Data;
|
||||
using tox::test::SimulatedEnvironment;
|
||||
|
||||
template <typename T>
|
||||
using Ptr = std::unique_ptr<T, void (*)(T *)>;
|
||||
|
||||
void setup_callbacks(Tox_Dispatch *dispatch)
|
||||
{
|
||||
tox_events_callback_conference_connected(
|
||||
@@ -131,9 +143,16 @@ void setup_callbacks(Tox_Dispatch *dispatch)
|
||||
|
||||
void TestEndToEnd(Fuzz_Data &input)
|
||||
{
|
||||
Fuzz_System sys(input);
|
||||
// Used for places where we want all allocations to succeed.
|
||||
Null_System null_sys;
|
||||
SimulatedEnvironment env;
|
||||
env.fake_clock().advance(1000000000); // Match legacy behavior
|
||||
auto node = env.create_node(33445);
|
||||
configure_fuzz_memory_source(env.fake_memory(), input);
|
||||
configure_fuzz_packet_source(*node->endpoint, input);
|
||||
configure_fuzz_random_source(env.fake_random(), input);
|
||||
|
||||
// Null system replacement for event comparison
|
||||
SimulatedEnvironment null_env;
|
||||
auto null_node = null_env.create_node(0);
|
||||
|
||||
Ptr<Tox_Options> opts(tox_options_new(nullptr), tox_options_free);
|
||||
assert(opts != nullptr);
|
||||
@@ -144,13 +163,31 @@ void TestEndToEnd(Fuzz_Data &input)
|
||||
const char *message, void *user_data) {
|
||||
// Log to stdout.
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf("[tox1] %c %s:%u(%s): %s\n", tox_log_level_name(level), file, line,
|
||||
func, message);
|
||||
// Approximate level name mapping
|
||||
char level_char = '?';
|
||||
switch (level) {
|
||||
case TOX_LOG_LEVEL_TRACE:
|
||||
level_char = 'T';
|
||||
break;
|
||||
case TOX_LOG_LEVEL_DEBUG:
|
||||
level_char = 'D';
|
||||
break;
|
||||
case TOX_LOG_LEVEL_INFO:
|
||||
level_char = 'I';
|
||||
break;
|
||||
case TOX_LOG_LEVEL_WARNING:
|
||||
level_char = 'W';
|
||||
break;
|
||||
case TOX_LOG_LEVEL_ERROR:
|
||||
level_char = 'E';
|
||||
break;
|
||||
}
|
||||
std::printf("[tox1] %c %s:%u(%s): %s\n", level_char, file, line, func, message);
|
||||
}
|
||||
});
|
||||
|
||||
Tox_Options_Testing tox_options_testing;
|
||||
tox_options_testing.operating_system = sys.sys.get();
|
||||
tox_options_testing.operating_system = &node->system;
|
||||
|
||||
Tox_Err_New error_new;
|
||||
Tox_Err_New_Testing error_new_testing;
|
||||
@@ -171,15 +208,18 @@ void TestEndToEnd(Fuzz_Data &input)
|
||||
assert(dispatch != nullptr);
|
||||
setup_callbacks(dispatch);
|
||||
|
||||
// MIN_ITERATION_INTERVAL = 20
|
||||
const uint8_t MIN_ITERATION_INTERVAL = 20;
|
||||
|
||||
while (!input.empty()) {
|
||||
Tox_Err_Events_Iterate error_iterate;
|
||||
Tox_Events *events = tox_events_iterate(tox, true, &error_iterate);
|
||||
assert(tox_events_equal(null_sys.sys.get(), events, events));
|
||||
assert(tox_events_equal(&null_node->system, events, events));
|
||||
tox_dispatch_invoke(dispatch, events, tox);
|
||||
tox_events_free(events);
|
||||
// Move the clock forward a decent amount so all the time-based checks
|
||||
// trigger more quickly.
|
||||
sys.clock += std::max(System::MIN_ITERATION_INTERVAL, random_u08(sys.rng.get()));
|
||||
|
||||
uint32_t rand_val = env.fake_random().uniform(256);
|
||||
env.fake_clock().advance(std::max<uint64_t>(MIN_ITERATION_INTERVAL, rand_val));
|
||||
}
|
||||
|
||||
tox_dispatch_free(dispatch);
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright © 2022-2025 The TokTok team.
|
||||
*/
|
||||
|
||||
#ifndef C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H
|
||||
#define C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename F, F f>
|
||||
struct func_conversion {
|
||||
private:
|
||||
template <typename R, typename... Args>
|
||||
using func_pointer = R (*)(Args...);
|
||||
|
||||
template <typename From>
|
||||
struct static_caster {
|
||||
From obj;
|
||||
|
||||
template <typename To>
|
||||
operator To() const
|
||||
{
|
||||
return static_cast<To>(obj);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename R, typename Arg, typename... Args>
|
||||
constexpr operator func_pointer<R, Arg, Args...>()
|
||||
{
|
||||
return [](Arg obj, auto... args) { return f(static_caster<Arg>{obj}, args...); };
|
||||
}
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct make_funptr;
|
||||
|
||||
template <typename T, typename R, typename... Args>
|
||||
struct make_funptr<R (T::*)(Args...) const> {
|
||||
using type = R (*)(Args...);
|
||||
};
|
||||
|
||||
/** @brief Turn a memfunptr type into a plain funptr type.
|
||||
*
|
||||
* Not needed in C++20, because we can pass the lambda itself as template
|
||||
* argument, but in C++17, we need to do an early conversion.
|
||||
*/
|
||||
template <typename F>
|
||||
using make_funptr_t = typename make_funptr<F>::type;
|
||||
|
||||
}
|
||||
|
||||
/** @brief Turn a C++ lambda into a C function pointer with `void*` param.
|
||||
*
|
||||
* Takes a lambda function with any pointer type as first parameter and turns it
|
||||
* into a C function pointer with `void*` as the first parameter. Internally, it
|
||||
* `static_cast`s that `void*` to the lambda's parameter type, avoiding a bunch
|
||||
* of casts inside the lambdas.
|
||||
*
|
||||
* This works on any type `T` that can be `static_cast` to `U`, not just `void*`
|
||||
* to `U*`, but the common case for C callbacks is `void*`.
|
||||
*/
|
||||
template <typename F>
|
||||
static constexpr auto operator!(F f)
|
||||
{
|
||||
return detail::func_conversion<detail::make_funptr_t<decltype(&F::operator())>, f>{};
|
||||
}
|
||||
|
||||
#endif // C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H
|
||||
@@ -1,479 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright © 2021-2025 The TokTok team.
|
||||
*/
|
||||
|
||||
#include "fuzz_support.hh"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
// Comment line here to avoid reordering by source code formatters.
|
||||
#include <windows.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "../../toxcore/crypto_core.h"
|
||||
#include "../../toxcore/network.h"
|
||||
#include "../../toxcore/tox_memory_impl.h"
|
||||
#include "../../toxcore/tox_private.h"
|
||||
#include "../../toxcore/tox_random_impl.h"
|
||||
#include "func_conversion.hh"
|
||||
|
||||
// TODO(iphydf): Put this somewhere shared.
|
||||
struct Network_Addr {
|
||||
struct sockaddr_storage addr;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
System::System(std::unique_ptr<Tox_System> in_sys, std::unique_ptr<Tox_Memory> in_mem,
|
||||
std::unique_ptr<Network> in_ns, std::unique_ptr<Tox_Random> in_rng)
|
||||
: sys(std::move(in_sys))
|
||||
, mem(std::move(in_mem))
|
||||
, ns(std::move(in_ns))
|
||||
, rng(std::move(in_rng))
|
||||
{
|
||||
}
|
||||
System::System(System &&) = default;
|
||||
|
||||
System::~System() { }
|
||||
|
||||
static int recv_common(Fuzz_Data &input, uint8_t *buf, size_t buf_len)
|
||||
{
|
||||
if (input.size() < 2) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
CONSUME_OR_ABORT(const uint8_t *fuzz_len_bytes, input, 2);
|
||||
const std::size_t fuzz_len = (fuzz_len_bytes[0] << 8) | fuzz_len_bytes[1];
|
||||
|
||||
if (fuzz_len == 0xffff) {
|
||||
errno = EWOULDBLOCK;
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf("recvfrom: no data for tox1\n");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf(
|
||||
"recvfrom: %zu (%02x, %02x) for tox1\n", fuzz_len, input.data()[-2], input.data()[-1]);
|
||||
}
|
||||
const size_t res = std::min(buf_len, std::min(fuzz_len, input.size()));
|
||||
|
||||
CONSUME_OR_ABORT(const uint8_t *data, input, res);
|
||||
std::copy(data, data + res, buf);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void *report_alloc(const char *name, const char *func, std::size_t size, void *ptr)
|
||||
{
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
printf("%s: %s(%zu): %s\n", name, func, size, ptr == nullptr ? "false" : "true");
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template <typename F, F Func, typename... Args>
|
||||
static void *alloc_common(const char *func, std::size_t size, Fuzz_Data &data, Args... args)
|
||||
{
|
||||
CONSUME1_OR_RETURN_VAL(
|
||||
const bool, want_alloc, data, report_alloc("tox1", func, size, Func(args...)));
|
||||
if (!want_alloc) {
|
||||
return nullptr;
|
||||
}
|
||||
return report_alloc("tox1", func, size, Func(args...));
|
||||
}
|
||||
|
||||
static constexpr Tox_Memory_Funcs fuzz_memory_funcs = {
|
||||
/* .malloc = */
|
||||
 {
|
||||
return alloc_common<decltype(std::malloc), std::malloc>("malloc", size, self->data, size);
|
||||
},
|
||||
/* .realloc = */
|
||||
 {
|
||||
return alloc_common<decltype(std::realloc), std::realloc>(
|
||||
"realloc", size, self->data, ptr, size);
|
||||
},
|
||||
/* .dealloc = */
|
||||
 { std::free(ptr); },
|
||||
};
|
||||
|
||||
static constexpr Network_Funcs fuzz_network_funcs = {
|
||||
/* .close = */  { return 0; },
|
||||
/* .accept = */  { return Socket{1337}; },
|
||||
/* .bind = */  { return 0; },
|
||||
/* .listen = */  { return 0; },
|
||||
/* .connect = */  { return 0; },
|
||||
/* .recvbuf = */
|
||||
 {
|
||||
assert(sock.value == 42 || sock.value == 1337);
|
||||
const size_t count = random_u16(self->rng.get());
|
||||
return static_cast<int>(std::min(count, self->data.size()));
|
||||
},
|
||||
/* .recv = */
|
||||
 {
|
||||
assert(sock.value == 42 || sock.value == 1337);
|
||||
// Receive data from the fuzzer.
|
||||
return recv_common(self->data, buf, len);
|
||||
},
|
||||
/* .recvfrom = */
|
||||
 {
|
||||
assert(sock.value == 42 || sock.value == 1337);
|
||||
|
||||
addr->addr = sockaddr_storage{};
|
||||
// Dummy Addr
|
||||
addr->addr.ss_family = AF_INET;
|
||||
|
||||
// We want an AF_INET address with dummy values
|
||||
sockaddr_in *addr_in = reinterpret_cast<sockaddr_in *>(&addr->addr);
|
||||
addr_in->sin_port = htons(33446);
|
||||
addr_in->sin_addr.s_addr = htonl(0x7f000002); // 127.0.0.2
|
||||
addr->size = sizeof(struct sockaddr);
|
||||
|
||||
return recv_common(self->data, buf, len);
|
||||
},
|
||||
/* .send = */
|
||||
 {
|
||||
assert(sock.value == 42 || sock.value == 1337);
|
||||
// Always succeed.
|
||||
return static_cast<int>(len);
|
||||
},
|
||||
/* .sendto = */
|
||||
 {
|
||||
assert(sock.value == 42 || sock.value == 1337);
|
||||
// Always succeed.
|
||||
return static_cast<int>(len);
|
||||
},
|
||||
/* .socket = */  { return Socket{42}; },
|
||||
/* .socket_nonblock = */  { return 0; },
|
||||
/* .getsockopt = */
|
||||
 {
|
||||
std::memset(optval, 0, *optlen);
|
||||
return 0;
|
||||
},
|
||||
/* .setsockopt = */
|
||||
 {
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
|
||||
static constexpr Tox_Random_Funcs fuzz_random_funcs = {
|
||||
/* .bytes_callback = */
|
||||
 {
|
||||
// Initialize the buffer with zeros in case there's no randomness left.
|
||||
std::fill_n(bytes, length, 0);
|
||||
|
||||
// For integers, we copy bytes directly, because we want to control the
|
||||
// exact values.
|
||||
if (length == sizeof(uint8_t) || length == sizeof(uint16_t) || length == sizeof(uint32_t)
|
||||
|| length == sizeof(uint64_t)) {
|
||||
CONSUME_OR_RETURN(const uint8_t *data, self->data, length);
|
||||
std::copy(data, data + length, bytes);
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
if (length == 1) {
|
||||
std::printf("rng: %d (0x%02x)\n", bytes[0], bytes[0]);
|
||||
} else {
|
||||
std::printf("rng: %02x..%02x[%zu]\n", bytes[0], bytes[length - 1], length);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For nonces and keys, we fill the buffer with the same 1-2 bytes
|
||||
// repeated. We only need these to be different enough to not often be
|
||||
// the same.
|
||||
assert(length == 24 || length == 32);
|
||||
// We must cover the case of having only 1 byte left in the input. In
|
||||
// that case, we will use the same byte for all the bytes in the output.
|
||||
const size_t chunk_size = std::max(self->data.size(), static_cast<std::size_t>(2));
|
||||
CONSUME_OR_RETURN(const uint8_t *chunk, self->data, chunk_size);
|
||||
if (chunk_size == 2) {
|
||||
std::fill_n(bytes, length / 2, chunk[0]);
|
||||
std::fill_n(bytes + length / 2, length / 2, chunk[1]);
|
||||
} else {
|
||||
std::fill_n(bytes, length, chunk[0]);
|
||||
}
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
if (length == 1) {
|
||||
std::printf("rng: %d (0x%02x)\n", bytes[0], bytes[0]);
|
||||
} else {
|
||||
std::printf("rng: %02x..%02x[%zu]\n", bytes[0], bytes[length - 1], length);
|
||||
}
|
||||
}
|
||||
},
|
||||
/* .uniform_callback = */
|
||||
 {
|
||||
uint32_t randnum = 0;
|
||||
if (upper_bound > 0) {
|
||||
self->rng->funcs->bytes_callback(
|
||||
self, reinterpret_cast<uint8_t *>(&randnum), sizeof(randnum));
|
||||
randnum %= upper_bound;
|
||||
}
|
||||
return randnum;
|
||||
},
|
||||
};
|
||||
|
||||
Fuzz_System::Fuzz_System(Fuzz_Data &input)
|
||||
: System{
|
||||
std::make_unique<Tox_System>(),
|
||||
std::make_unique<Tox_Memory>(Tox_Memory{&fuzz_memory_funcs, this}),
|
||||
std::make_unique<Network>(Network{&fuzz_network_funcs, this}),
|
||||
std::make_unique<Tox_Random>(Tox_Random{&fuzz_random_funcs, this}),
|
||||
}
|
||||
, data(input)
|
||||
{
|
||||
sys->mono_time_callback = [](void *self) { return static_cast<Fuzz_System *>(self)->clock; };
|
||||
sys->mono_time_user_data = this;
|
||||
sys->mem = mem.get();
|
||||
sys->ns = ns.get();
|
||||
sys->rng = rng.get();
|
||||
}
|
||||
|
||||
static constexpr Tox_Memory_Funcs null_memory_funcs = {
|
||||
/* .malloc = */
|
||||
 { return std::malloc(size); },
|
||||
/* .realloc = */
|
||||
 { return std::realloc(ptr, size); },
|
||||
/* .dealloc = */
|
||||
 { std::free(ptr); },
|
||||
};
|
||||
|
||||
static constexpr Network_Funcs null_network_funcs = {
|
||||
/* .close = */  { return 0; },
|
||||
/* .accept = */  { return Socket{1337}; },
|
||||
/* .bind = */  { return 0; },
|
||||
/* .listen = */  { return 0; },
|
||||
/* .connect = */  { return 0; },
|
||||
/* .recvbuf = */  { return 0; },
|
||||
/* .recv = */
|
||||
 {
|
||||
// Always fail.
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
},
|
||||
/* .recvfrom = */
|
||||
 {
|
||||
// Always fail.
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
},
|
||||
/* .send = */
|
||||
 {
|
||||
// Always succeed.
|
||||
return static_cast<int>(len);
|
||||
},
|
||||
/* .sendto = */
|
||||
 {
|
||||
// Always succeed.
|
||||
return static_cast<int>(len);
|
||||
},
|
||||
/* .socket = */  { return Socket{42}; },
|
||||
/* .socket_nonblock = */  { return 0; },
|
||||
/* .getsockopt = */
|
||||
 {
|
||||
std::memset(optval, 0, *optlen);
|
||||
return 0;
|
||||
},
|
||||
/* .setsockopt = */
|
||||
 {
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
|
||||
static uint64_t simple_rng(uint64_t &seed)
|
||||
{
|
||||
// https://nuclear.llnl.gov/CNP/rng/rngman/node4.html
|
||||
seed = 2862933555777941757LL * seed + 3037000493LL;
|
||||
return seed;
|
||||
}
|
||||
|
||||
static constexpr Tox_Random_Funcs null_random_funcs = {
|
||||
/* .bytes_callback = */
|
||||
 {
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
bytes[i] = simple_rng(self->seed) & 0xff;
|
||||
}
|
||||
},
|
||||
/* .uniform_callback = */
|
||||
 {
|
||||
return static_cast<uint32_t>(simple_rng(self->seed)) % upper_bound;
|
||||
},
|
||||
};
|
||||
|
||||
Null_System::Null_System()
|
||||
: System{
|
||||
std::make_unique<Tox_System>(),
|
||||
std::make_unique<Tox_Memory>(Tox_Memory{&null_memory_funcs, this}),
|
||||
std::make_unique<Network>(Network{&null_network_funcs, this}),
|
||||
std::make_unique<Tox_Random>(Tox_Random{&null_random_funcs, this}),
|
||||
}
|
||||
{
|
||||
sys->mono_time_callback = [](void *self) { return static_cast<Null_System *>(self)->clock; };
|
||||
sys->mono_time_user_data = this;
|
||||
sys->mem = mem.get();
|
||||
sys->ns = ns.get();
|
||||
sys->rng = rng.get();
|
||||
}
|
||||
|
||||
static uint16_t get_port(const Network_Addr *addr)
|
||||
{
|
||||
if (addr->addr.ss_family == AF_INET6) {
|
||||
return reinterpret_cast<const sockaddr_in6 *>(&addr->addr)->sin6_port;
|
||||
} else {
|
||||
assert(addr->addr.ss_family == AF_INET);
|
||||
return reinterpret_cast<const sockaddr_in *>(&addr->addr)->sin_port;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr Tox_Memory_Funcs record_memory_funcs = {
|
||||
/* .malloc = */
|
||||
 {
|
||||
self->push(true);
|
||||
return report_alloc(self->name_, "malloc", size, std::malloc(size));
|
||||
},
|
||||
/* .realloc = */
|
||||
 {
|
||||
self->push(true);
|
||||
return report_alloc(self->name_, "realloc", size, std::realloc(ptr, size));
|
||||
},
|
||||
/* .dealloc = */
|
||||
 { std::free(ptr); },
|
||||
};
|
||||
|
||||
static constexpr Network_Funcs record_network_funcs = {
|
||||
/* .close = */  { return 0; },
|
||||
/* .accept = */  { return Socket{2}; },
|
||||
/* .bind = */
|
||||
 {
|
||||
const uint16_t port = get_port(addr);
|
||||
if (self->global_.bound.find(port) != self->global_.bound.end()) {
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
self->global_.bound.emplace(port, self);
|
||||
self->port = port;
|
||||
return 0;
|
||||
},
|
||||
/* .listen = */  { return 0; },
|
||||
/* .connect = */  { return 0; },
|
||||
/* .recvbuf = */  { return 0; },
|
||||
/* .recv = */
|
||||
 {
|
||||
// Always fail.
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
},
|
||||
/* .recvfrom = */
|
||||
 {
|
||||
assert(sock.value == 42);
|
||||
if (self->recvq.empty()) {
|
||||
self->push("\xff\xff");
|
||||
errno = EWOULDBLOCK;
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf("%s: recvfrom: no data\n", self->name_);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
const auto [from, packet] = std::move(self->recvq.front());
|
||||
self->recvq.pop_front();
|
||||
const size_t recvlen = std::min(len, packet.size());
|
||||
std::copy(packet.begin(), packet.end(), buf);
|
||||
|
||||
addr->addr = sockaddr_storage{};
|
||||
// Dummy Addr
|
||||
addr->addr.ss_family = AF_INET;
|
||||
|
||||
// We want an AF_INET address with dummy values
|
||||
sockaddr_in *addr_in = reinterpret_cast<sockaddr_in *>(&addr->addr);
|
||||
addr_in->sin_port = from;
|
||||
addr_in->sin_addr.s_addr = htonl(0x7f000002); // 127.0.0.2
|
||||
addr->size = sizeof(struct sockaddr);
|
||||
|
||||
assert(recvlen > 0 && recvlen <= INT_MAX);
|
||||
self->push(uint8_t(recvlen >> 8));
|
||||
self->push(uint8_t(recvlen & 0xff));
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf("%s: recvfrom: %zu (%02x, %02x)\n", self->name_, recvlen,
|
||||
self->recording().end()[-2], self->recording().end()[-1]);
|
||||
}
|
||||
self->push(buf, recvlen);
|
||||
return static_cast<int>(recvlen);
|
||||
},
|
||||
/* .send = */
|
||||
 {
|
||||
// Always succeed.
|
||||
return static_cast<int>(len);
|
||||
},
|
||||
/* .sendto = */
|
||||
 {
|
||||
assert(sock.value == 42);
|
||||
auto backend = self->global_.bound.find(get_port(addr));
|
||||
assert(backend != self->global_.bound.end());
|
||||
backend->second->receive(self->port, buf, len);
|
||||
return static_cast<int>(len);
|
||||
},
|
||||
/* .socket = */
|
||||
 { return Socket{42}; },
|
||||
/* .socket_nonblock = */  { return 0; },
|
||||
/* .getsockopt = */
|
||||
 {
|
||||
std::memset(optval, 0, *optlen);
|
||||
return 0;
|
||||
},
|
||||
/* .setsockopt = */
|
||||
 { return 0; },
|
||||
};
|
||||
|
||||
static constexpr Tox_Random_Funcs record_random_funcs = {
|
||||
/* .bytes_callback = */
|
||||
 {
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
bytes[i] = simple_rng(self->seed_) & 0xff;
|
||||
self->push(bytes[i]);
|
||||
}
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf(
|
||||
"%s: rng: %02x..%02x[%zu]\n", self->name_, bytes[0], bytes[length - 1], length);
|
||||
}
|
||||
},
|
||||
/* .uniform_callback = */
|
||||
fuzz_random_funcs.uniform_callback,
|
||||
};
|
||||
|
||||
Record_System::Record_System(Global &global, uint64_t seed, const char *name)
|
||||
: System{
|
||||
std::make_unique<Tox_System>(),
|
||||
std::make_unique<Tox_Memory>(Tox_Memory{&record_memory_funcs, this}),
|
||||
std::make_unique<Network>(Network{&record_network_funcs, this}),
|
||||
std::make_unique<Random>(Random{&record_random_funcs, this}),
|
||||
}
|
||||
, global_(global)
|
||||
, seed_(seed)
|
||||
, name_(name)
|
||||
{
|
||||
sys->mono_time_callback = [](void *self) { return static_cast<Record_System *>(self)->clock; };
|
||||
sys->mono_time_user_data = this;
|
||||
sys->mem = mem.get();
|
||||
sys->ns = ns.get();
|
||||
sys->rng = rng.get();
|
||||
}
|
||||
|
||||
void Record_System::receive(uint16_t send_port, const uint8_t *buf, size_t len)
|
||||
{
|
||||
assert(port != 0);
|
||||
recvq.emplace_back(send_port, std::vector<uint8_t>{buf, buf + len});
|
||||
}
|
||||
@@ -1,407 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright © 2021-2025 The TokTok team.
|
||||
*/
|
||||
|
||||
#ifndef C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H
|
||||
#define C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "../../toxcore/tox_private.h"
|
||||
|
||||
struct Fuzz_Data {
|
||||
static constexpr bool FUZZ_DEBUG = false;
|
||||
static constexpr std::size_t TRACE_TRAP = -1; // 579;
|
||||
|
||||
private:
|
||||
const uint8_t *data_;
|
||||
const uint8_t *base_;
|
||||
std::size_t size_;
|
||||
|
||||
public:
|
||||
Fuzz_Data(const uint8_t *input_data, std::size_t input_size)
|
||||
: data_(input_data)
|
||||
, base_(input_data)
|
||||
, size_(input_size)
|
||||
{
|
||||
}
|
||||
|
||||
Fuzz_Data &operator=(const Fuzz_Data &rhs) = delete;
|
||||
Fuzz_Data(const Fuzz_Data &rhs) = delete;
|
||||
|
||||
struct Consumer {
|
||||
const char *func;
|
||||
Fuzz_Data &fd;
|
||||
|
||||
operator bool()
|
||||
{
|
||||
// Special case because memcpy causes UB for bool (which can't be
|
||||
// anything other than 0 or 1).
|
||||
const bool val = fd.data_[0];
|
||||
if (FUZZ_DEBUG) {
|
||||
std::printf("consume@%zu(%s): bool %s\n", fd.pos(), func, val ? "true" : "false");
|
||||
}
|
||||
++fd.data_;
|
||||
--fd.size_;
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
operator T()
|
||||
{
|
||||
const uint8_t *bytes = fd.consume(func, sizeof(T));
|
||||
T val;
|
||||
std::memcpy(&val, bytes, sizeof(T));
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
Consumer consume1(const char *func) { return Consumer{func, *this}; }
|
||||
std::size_t size() const { return size_; }
|
||||
std::size_t pos() const { return data_ - base_; }
|
||||
const uint8_t *data() const { return data_; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
|
||||
const uint8_t *consume(const char *func, std::size_t count)
|
||||
{
|
||||
const uint8_t *val = data_;
|
||||
if (FUZZ_DEBUG) {
|
||||
if (pos() == TRACE_TRAP) {
|
||||
__asm__("int $3");
|
||||
}
|
||||
if (count == 1) {
|
||||
std::printf("consume@%zu(%s): %d (0x%02x)\n", pos(), func, val[0], val[0]);
|
||||
} else if (count != 0) {
|
||||
std::printf("consume@%zu(%s): %02x..%02x[%zu]\n", pos(), func, val[0],
|
||||
val[count - 1], count);
|
||||
}
|
||||
}
|
||||
data_ += count;
|
||||
size_ -= count;
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
/** @brief Consumes 1 byte of the fuzzer input or returns if no data available.
|
||||
*
|
||||
* This advances the fuzzer input data by 1 byte and consumes that byte in the
|
||||
* declaration.
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* CONSUME1_OR_RETURN(const uint8_t, one_byte, input);
|
||||
* @endcode
|
||||
*/
|
||||
#define CONSUME1_OR_RETURN(TYPE, NAME, INPUT) \
|
||||
if (INPUT.size() < sizeof(TYPE)) { \
|
||||
return; \
|
||||
} \
|
||||
TYPE NAME = INPUT.consume1(__func__)
|
||||
|
||||
/** @brief Consumes 1 byte of the fuzzer input or returns a value if no data
|
||||
* available.
|
||||
*
|
||||
* This advances the fuzzer input data by 1 byte and consumes that byte in the
|
||||
* declaration.
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* CONSUME1_OR_RETURN_VAL(const uint8_t one_byte, input, nullptr);
|
||||
* @endcode
|
||||
*/
|
||||
#define CONSUME1_OR_RETURN_VAL(TYPE, NAME, INPUT, VAL) \
|
||||
if (INPUT.size() < sizeof(TYPE)) { \
|
||||
return VAL; \
|
||||
} \
|
||||
TYPE NAME = INPUT.consume1(__func__)
|
||||
|
||||
/** @brief Consumes SIZE bytes of the fuzzer input or returns if not enough data available.
|
||||
*
|
||||
* This advances the fuzzer input data by SIZE byte and consumes those bytes in
|
||||
* the declaration. If less than SIZE bytes are available in the fuzzer input,
|
||||
* this macro returns from the enclosing function.
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* CONSUME_OR_RETURN(const uint8_t *ten_bytes, input, 10);
|
||||
* @endcode
|
||||
*/
|
||||
#define CONSUME_OR_RETURN(DECL, INPUT, SIZE) \
|
||||
if (INPUT.size() < SIZE) { \
|
||||
return; \
|
||||
} \
|
||||
DECL = INPUT.consume(__func__, SIZE)
|
||||
|
||||
#define CONSUME_OR_RETURN_VAL(DECL, INPUT, SIZE, VAL) \
|
||||
if (INPUT.size() < SIZE) { \
|
||||
return VAL; \
|
||||
} \
|
||||
DECL = INPUT.consume(__func__, SIZE)
|
||||
|
||||
#define CONSUME_OR_ABORT(DECL, INPUT, SIZE) \
|
||||
if (INPUT.size() < SIZE) { \
|
||||
abort(); \
|
||||
} \
|
||||
DECL = INPUT.consume(__func__, SIZE)
|
||||
|
||||
using Fuzz_Target = void (*)(Fuzz_Data &input);
|
||||
|
||||
template <Fuzz_Target... Args>
|
||||
struct Fuzz_Target_Selector;
|
||||
|
||||
template <Fuzz_Target Arg, Fuzz_Target... Args>
|
||||
struct Fuzz_Target_Selector<Arg, Args...> {
|
||||
static void select(uint8_t selector, Fuzz_Data &input)
|
||||
{
|
||||
if (selector == sizeof...(Args)) {
|
||||
return Arg(input);
|
||||
}
|
||||
return Fuzz_Target_Selector<Args...>::select(selector, input);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Fuzz_Target_Selector<> {
|
||||
static void select(uint8_t selector, Fuzz_Data &input)
|
||||
{
|
||||
// The selector selected no function, so we do nothing and rely on the
|
||||
// fuzzer to come up with a better selector.
|
||||
}
|
||||
};
|
||||
|
||||
template <Fuzz_Target... Args>
|
||||
void fuzz_select_target(const uint8_t *data, std::size_t size)
|
||||
{
|
||||
Fuzz_Data input{data, size};
|
||||
|
||||
CONSUME1_OR_RETURN(const uint8_t, selector, input);
|
||||
return Fuzz_Target_Selector<Args...>::select(selector, input);
|
||||
}
|
||||
|
||||
struct Tox_Memory;
|
||||
struct Network;
|
||||
struct Tox_Random;
|
||||
|
||||
struct System {
|
||||
/** @brief Deterministic system clock for this instance.
|
||||
*
|
||||
* Different instances can evolve independently. The time is initialised
|
||||
* with a large number, because otherwise many zero-initialised "empty"
|
||||
* friends inside toxcore will be "not timed out" for a long time, messing
|
||||
* up some logic. Tox moderately depends on the clock being fairly high up
|
||||
* (not close to 0).
|
||||
*
|
||||
* We make it a nice large round number so we can recognise it when debugging.
|
||||
*/
|
||||
uint64_t clock = 1000000000;
|
||||
|
||||
std::unique_ptr<Tox_System> sys;
|
||||
std::unique_ptr<Tox_Memory> mem;
|
||||
std::unique_ptr<Network> ns;
|
||||
std::unique_ptr<Tox_Random> rng;
|
||||
|
||||
System(std::unique_ptr<Tox_System> sys, std::unique_ptr<Tox_Memory> mem,
|
||||
std::unique_ptr<Network> ns, std::unique_ptr<Tox_Random> rng);
|
||||
System(System &&);
|
||||
|
||||
// Not inline because sizeof of the above 2 structs is not known everywhere.
|
||||
~System();
|
||||
|
||||
/**
|
||||
* During bootstrap, move the time forward a decent amount, because friend
|
||||
* finding and bootstrapping takes significant (around 10 seconds) wall
|
||||
* clock time that should be advanced more quickly in the test.
|
||||
*/
|
||||
static constexpr uint8_t BOOTSTRAP_ITERATION_INTERVAL = 200;
|
||||
/**
|
||||
* Less than BOOTSTRAP_ITERATION_INTERVAL because otherwise we'll spam
|
||||
* onion announce packets.
|
||||
*/
|
||||
static constexpr uint8_t MESSAGE_ITERATION_INTERVAL = 20;
|
||||
/**
|
||||
* Move the clock forward at least 20ms so at least some amount of
|
||||
* time passes on each iteration.
|
||||
*/
|
||||
static constexpr uint8_t MIN_ITERATION_INTERVAL = 20;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Tox_System implementation that consumes fuzzer input to produce network
|
||||
* inputs and random numbers. Once it runs out of fuzzer input, network receive
|
||||
* functions return no more data and the random numbers are always zero.
|
||||
*/
|
||||
struct Fuzz_System : System {
|
||||
Fuzz_Data &data;
|
||||
|
||||
explicit Fuzz_System(Fuzz_Data &input);
|
||||
};
|
||||
|
||||
/**
|
||||
* A Tox_System implementation that consumes no fuzzer input but still has a
|
||||
* working and deterministic RNG. Network receive functions always fail, send
|
||||
* always succeeds.
|
||||
*/
|
||||
struct Null_System : System {
|
||||
uint64_t seed = 4; // chosen by fair dice roll. guaranteed to be random.
|
||||
|
||||
Null_System();
|
||||
};
|
||||
|
||||
template <typename V>
|
||||
class int_map {
|
||||
public:
|
||||
struct iterator {
|
||||
std::pair<uint16_t, V> pair;
|
||||
|
||||
bool operator==(const iterator &rhs) const { return pair.first == rhs.pair.first; }
|
||||
bool operator!=(const iterator &rhs) const { return pair.first != rhs.pair.first; }
|
||||
|
||||
std::pair<uint16_t, V> operator*() const { return pair; }
|
||||
const std::pair<uint16_t, V> *operator->() const { return &pair; }
|
||||
};
|
||||
|
||||
int_map() = default;
|
||||
~int_map() = default;
|
||||
|
||||
iterator find(uint16_t key) const
|
||||
{
|
||||
if (!values[key]) {
|
||||
return end();
|
||||
}
|
||||
return {{key, values[key]}};
|
||||
}
|
||||
|
||||
iterator end() const { return {{static_cast<uint16_t>(values.size()), nullptr}}; }
|
||||
|
||||
void emplace(uint16_t key, V value) { values[key] = value; }
|
||||
|
||||
private:
|
||||
std::array<V, UINT16_MAX> values;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Tox_System implementation that records all I/O but does not actually
|
||||
* perform any real I/O. Everything inside this system is hermetic in-process
|
||||
* and fully deterministic.
|
||||
*
|
||||
* Note: take care not to initialise two systems with the same seed, since
|
||||
* that's the only thing distinguishing the system's behaviour. Two toxes
|
||||
* initialised with the same seed will be identical (same keys, etc.).
|
||||
*/
|
||||
struct Record_System : System {
|
||||
static constexpr bool FUZZ_DEBUG = Fuzz_Data::FUZZ_DEBUG;
|
||||
|
||||
/** @brief State shared between all tox instances. */
|
||||
struct Global {
|
||||
/** @brief Bound UDP ports and their system instance.
|
||||
*
|
||||
* This implements an in-process network where instances can send
|
||||
* packets to other instances by inserting them into the receiver's
|
||||
* recvq using the receive function.
|
||||
*
|
||||
* We need to keep track of ports associated with recv queues because
|
||||
* toxcore sends packets to itself sometimes when doing onion routing
|
||||
* with only 2 nodes in the network.
|
||||
*/
|
||||
int_map<Record_System *> bound;
|
||||
};
|
||||
|
||||
Global &global_;
|
||||
uint64_t seed_; //!< Current PRNG state.
|
||||
const char *name_; //!< Tox system name ("tox1"/"tox2") for logging.
|
||||
|
||||
std::deque<std::pair<uint16_t, std::vector<uint8_t>>> recvq;
|
||||
uint16_t port = 0; //!< Sending port for this system instance.
|
||||
|
||||
Record_System(Global &global, uint64_t seed, const char *name);
|
||||
Record_System(const Record_System &) = delete;
|
||||
Record_System operator=(const Record_System &) = delete;
|
||||
|
||||
/** @brief Deposit a network packet in this instance's recvq.
|
||||
*/
|
||||
void receive(uint16_t send_port, const uint8_t *buf, size_t len);
|
||||
|
||||
void push(bool byte)
|
||||
{
|
||||
if (FUZZ_DEBUG) {
|
||||
if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
|
||||
__asm__("int $3");
|
||||
}
|
||||
std::printf(
|
||||
"%s: produce@%zu(bool %s)\n", name_, recording_.size(), byte ? "true" : "false");
|
||||
}
|
||||
recording_.push_back(byte);
|
||||
}
|
||||
|
||||
void push(uint8_t byte)
|
||||
{
|
||||
if (FUZZ_DEBUG) {
|
||||
if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
|
||||
__asm__("int $3");
|
||||
}
|
||||
std::printf("%s: produce@%zu(%u (0x%02x))\n", name_, recording_.size(), byte, byte);
|
||||
}
|
||||
recording_.push_back(byte);
|
||||
}
|
||||
|
||||
void push(const uint8_t *bytes, std::size_t size)
|
||||
{
|
||||
if (FUZZ_DEBUG) {
|
||||
if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
|
||||
__asm__("int $3");
|
||||
}
|
||||
std::printf("%s: produce@%zu(%02x..%02x[%zu])\n", name_, recording_.size(), bytes[0],
|
||||
bytes[size - 1], size);
|
||||
}
|
||||
recording_.insert(recording_.end(), bytes, bytes + size);
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
void push(const char (&bytes)[N])
|
||||
{
|
||||
push(reinterpret_cast<const uint8_t *>(bytes), N - 1);
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> &recording() const { return recording_; }
|
||||
std::vector<uint8_t> take_recording() const { return std::move(recording_); }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> recording_;
|
||||
};
|
||||
|
||||
/** @brief Enable debug logging.
|
||||
*
|
||||
* This should not be enabled in fuzzer code while fuzzing, as console I/O slows
|
||||
* everything down drastically. It's useful while developing the fuzzer and the
|
||||
* protodump program.
|
||||
*/
|
||||
extern const bool FUZZ_DEBUG;
|
||||
|
||||
inline constexpr char tox_log_level_name(Tox_Log_Level level)
|
||||
{
|
||||
switch (level) {
|
||||
case TOX_LOG_LEVEL_TRACE:
|
||||
return 'T';
|
||||
case TOX_LOG_LEVEL_DEBUG:
|
||||
return 'D';
|
||||
case TOX_LOG_LEVEL_INFO:
|
||||
return 'I';
|
||||
case TOX_LOG_LEVEL_WARNING:
|
||||
return 'W';
|
||||
case TOX_LOG_LEVEL_ERROR:
|
||||
return 'E';
|
||||
}
|
||||
|
||||
return '?';
|
||||
}
|
||||
|
||||
#endif // C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H
|
||||
@@ -1,17 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright © 2022-2025 The TokTok team.
|
||||
*/
|
||||
|
||||
#ifndef C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H
|
||||
#define C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../toxcore/network.h"
|
||||
|
||||
constexpr uint16_t SIZE_IP_PORT = SIZE_IP6 + sizeof(uint16_t);
|
||||
|
||||
template <typename T>
|
||||
using Ptr = std::unique_ptr<T, void (*)(T *)>;
|
||||
|
||||
#endif // C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H
|
||||
@@ -26,23 +26,41 @@
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "../../toxcore/tox.h"
|
||||
#include "../../toxcore/tox_dispatch.h"
|
||||
#include "../../toxcore/tox_events.h"
|
||||
#include "../../toxcore/tox_private.h"
|
||||
#include "fuzz_support.hh"
|
||||
#include "../support/public/simulated_environment.hh"
|
||||
|
||||
namespace {
|
||||
|
||||
/** @brief Number of messages to exchange between tox1 and tox2.
|
||||
*
|
||||
* The higher this number, the more room we give the fuzzer to mutate the
|
||||
* exchange into something more interesting. If it's too high, the fuzzer will
|
||||
* be slow.
|
||||
*/
|
||||
using tox::test::FakeClock;
|
||||
using tox::test::Packet;
|
||||
using tox::test::ScopedToxSystem;
|
||||
using tox::test::SimulatedEnvironment;
|
||||
|
||||
constexpr uint32_t MESSAGE_COUNT = 5;
|
||||
|
||||
class Recorder {
|
||||
public:
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
void push(bool val) { data.push_back(val); }
|
||||
void push(uint8_t val) { data.push_back(val); }
|
||||
void push(const uint8_t *bytes, size_t size) { data.insert(data.end(), bytes, bytes + size); }
|
||||
|
||||
// Format: 2 bytes length (big-endian), then data.
|
||||
void push_packet(const uint8_t *bytes, size_t size)
|
||||
{
|
||||
assert(size <= 65535);
|
||||
push(static_cast<uint8_t>(size >> 8));
|
||||
push(static_cast<uint8_t>(size & 0xFF));
|
||||
push(bytes, size);
|
||||
}
|
||||
};
|
||||
|
||||
struct State {
|
||||
Tox *tox;
|
||||
uint32_t done;
|
||||
@@ -91,7 +109,6 @@ void setup_callbacks(Tox_Dispatch *dispatch)
|
||||
tox_events_callback_friend_connection_status(
|
||||
dispatch, [](const Tox_Event_Friend_Connection_Status *event, void *user_data) {
|
||||
State *state = static_cast<State *>(user_data);
|
||||
// OK: friend came online.
|
||||
const uint32_t friend_number
|
||||
= tox_event_friend_connection_status_get_friend_number(event);
|
||||
assert(friend_number == 0);
|
||||
@@ -163,9 +180,7 @@ void setup_callbacks(Tox_Dispatch *dispatch)
|
||||
assert(!tox_event_friend_typing_get_typing(event));
|
||||
});
|
||||
tox_events_callback_self_connection_status(
|
||||
dispatch, [](const Tox_Event_Self_Connection_Status *event, void *user_data) {
|
||||
// OK: we got connected.
|
||||
});
|
||||
dispatch, [](const Tox_Event_Self_Connection_Status *event, void *user_data) {});
|
||||
}
|
||||
|
||||
void dump(std::vector<uint8_t> recording, const char *filename)
|
||||
@@ -177,69 +192,80 @@ void dump(std::vector<uint8_t> recording, const char *filename)
|
||||
|
||||
void RecordBootstrap(const char *init, const char *bootstrap)
|
||||
{
|
||||
auto global = std::make_unique<Record_System::Global>();
|
||||
SimulatedEnvironment env1;
|
||||
SimulatedEnvironment env2;
|
||||
|
||||
// Set deterministic seeds.
|
||||
std::minstd_rand rng1(4);
|
||||
env1.fake_random().set_entropy_source([&](uint8_t *out, size_t count) {
|
||||
std::uniform_int_distribution<uint16_t> dist(0, 255);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
out[i] = static_cast<uint8_t>(dist(rng1));
|
||||
});
|
||||
|
||||
std::minstd_rand rng2(5);
|
||||
env2.fake_random().set_entropy_source([&](uint8_t *out, size_t count) {
|
||||
std::uniform_int_distribution<uint16_t> dist(0, 255);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
out[i] = static_cast<uint8_t>(dist(rng2));
|
||||
});
|
||||
|
||||
Recorder recorder1;
|
||||
Recorder recorder2;
|
||||
|
||||
env1.fake_memory().set_observer([&](bool success) { recorder1.push(success); });
|
||||
|
||||
env1.fake_random().set_observer(
|
||||
[&](const uint8_t *data, size_t count) { recorder1.push(data, count); });
|
||||
|
||||
auto node1 = env1.create_node(33445);
|
||||
auto node2 = env2.create_node(33446);
|
||||
|
||||
// Record received packets.
|
||||
node1->endpoint->set_recv_observer([&](const std::vector<uint8_t> &data, const IP_Port &from) {
|
||||
recorder1.push_packet(data.data(), data.size());
|
||||
});
|
||||
|
||||
// Bridge the two simulated networks.
|
||||
env1.simulation().net().add_observer(
|
||||
[&](const Packet &p) { env2.simulation().net().send_packet(p); });
|
||||
|
||||
env2.simulation().net().add_observer(
|
||||
[&](const Packet &p) { env1.simulation().net().send_packet(p); });
|
||||
|
||||
Tox_Options *opts = tox_options_new(nullptr);
|
||||
assert(opts != nullptr);
|
||||
|
||||
tox_options_set_local_discovery_enabled(opts, false);
|
||||
|
||||
tox_options_set_log_callback(opts,
|
||||
[](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func,
|
||||
const char *message, void *user_data) {
|
||||
// Log to stdout.
|
||||
std::printf("[%s] %c %s:%d(%s): %s\n", static_cast<Record_System *>(user_data)->name_,
|
||||
tox_log_level_name(level), file, line, func, message);
|
||||
});
|
||||
Tox_Options_Testing opts_test1;
|
||||
opts_test1.operating_system = &node1->system;
|
||||
|
||||
Tox_Err_New error_new;
|
||||
Tox_Err_New_Testing error_new_testing;
|
||||
Tox_Options_Testing tox_options_testing;
|
||||
|
||||
auto sys1 = std::make_unique<Record_System>(*global, 4, "tox1"); // fair dice roll
|
||||
tox_options_set_log_user_data(opts, sys1.get());
|
||||
tox_options_testing.operating_system = sys1->sys.get();
|
||||
Tox *tox1 = tox_new_testing(opts, &error_new, &tox_options_testing, &error_new_testing);
|
||||
Tox *tox1 = tox_new_testing(opts, &error_new, &opts_test1, &error_new_testing);
|
||||
assert(tox1 != nullptr);
|
||||
assert(error_new == TOX_ERR_NEW_OK);
|
||||
assert(error_new_testing == TOX_ERR_NEW_TESTING_OK);
|
||||
std::array<uint8_t, TOX_ADDRESS_SIZE> address1;
|
||||
tox_self_get_address(tox1, address1.data());
|
||||
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> pk1;
|
||||
tox_self_get_public_key(tox1, pk1.data());
|
||||
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> dht_key1;
|
||||
tox_self_get_dht_id(tox1, dht_key1.data());
|
||||
|
||||
auto sys2 = std::make_unique<Record_System>(*global, 5, "tox2"); // unfair dice roll
|
||||
tox_options_set_log_user_data(opts, sys2.get());
|
||||
tox_options_testing.operating_system = sys2->sys.get();
|
||||
Tox *tox2 = tox_new_testing(opts, &error_new, &tox_options_testing, &error_new_testing);
|
||||
Tox_Options_Testing opts_test2;
|
||||
opts_test2.operating_system = &node2->system;
|
||||
|
||||
Tox *tox2 = tox_new_testing(opts, &error_new, &opts_test2, &error_new_testing);
|
||||
assert(tox2 != nullptr);
|
||||
assert(error_new == TOX_ERR_NEW_OK);
|
||||
assert(error_new_testing == TOX_ERR_NEW_TESTING_OK);
|
||||
std::array<uint8_t, TOX_ADDRESS_SIZE> address2;
|
||||
tox_self_get_address(tox2, address2.data());
|
||||
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> pk2;
|
||||
tox_self_get_public_key(tox2, pk2.data());
|
||||
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> dht_key2;
|
||||
tox_self_get_dht_id(tox2, dht_key2.data());
|
||||
|
||||
assert(address1 != address2);
|
||||
assert(pk1 != pk2);
|
||||
assert(dht_key1 != dht_key2);
|
||||
|
||||
tox_options_free(opts);
|
||||
|
||||
const uint16_t port = tox_self_get_udp_port(tox1, nullptr);
|
||||
std::array<uint8_t, TOX_ADDRESS_SIZE> address1;
|
||||
tox_self_get_address(tox1, address1.data());
|
||||
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> dht_key1;
|
||||
tox_self_get_dht_id(tox1, dht_key1.data());
|
||||
|
||||
const bool udp_success = tox_bootstrap(tox2, "127.0.0.2", port, dht_key1.data(), nullptr);
|
||||
// Bootstrap tox2 to tox1.
|
||||
const bool udp_success = tox_bootstrap(tox2, "127.0.0.1", 33445, dht_key1.data(), nullptr);
|
||||
assert(udp_success);
|
||||
|
||||
tox_events_init(tox1);
|
||||
tox_events_init(tox2);
|
||||
|
||||
Tox_Dispatch *dispatch = tox_dispatch_new(nullptr);
|
||||
assert(dispatch != nullptr);
|
||||
setup_callbacks(dispatch);
|
||||
|
||||
State state1 = {tox1, 0};
|
||||
@@ -250,35 +276,26 @@ void RecordBootstrap(const char *init, const char *bootstrap)
|
||||
Tox_Events *events;
|
||||
|
||||
events = tox_events_iterate(tox1, true, &error_iterate);
|
||||
assert(tox_events_equal(sys1->sys.get(), events, events));
|
||||
tox_dispatch_invoke(dispatch, events, &state1);
|
||||
tox_events_free(events);
|
||||
|
||||
events = tox_events_iterate(tox2, true, &error_iterate);
|
||||
assert(tox_events_equal(sys2->sys.get(), events, events));
|
||||
tox_dispatch_invoke(dispatch, events, &state2);
|
||||
tox_events_free(events);
|
||||
|
||||
// Move the clock forward a decent amount so all the time-based checks
|
||||
// trigger more quickly.
|
||||
sys1->clock += clock_increment;
|
||||
sys2->clock += clock_increment;
|
||||
// Record the clock increment.
|
||||
env1.fake_clock().advance(clock_increment);
|
||||
recorder1.push(clock_increment);
|
||||
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
printf("tox1: rng: %d (for clock)\n", clock_increment);
|
||||
printf("tox2: rng: %d (for clock)\n", clock_increment);
|
||||
}
|
||||
sys1->push(clock_increment);
|
||||
sys2->push(clock_increment);
|
||||
env2.fake_clock().advance(clock_increment);
|
||||
|
||||
env1.simulation().net().process_events(env1.fake_clock().current_time_ms());
|
||||
env2.simulation().net().process_events(env2.fake_clock().current_time_ms());
|
||||
};
|
||||
|
||||
while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE
|
||||
|| tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE) {
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf("tox1: %d, tox2: %d\n", tox_self_get_connection_status(tox1),
|
||||
tox_self_get_connection_status(tox2));
|
||||
}
|
||||
iterate(System::BOOTSTRAP_ITERATION_INTERVAL);
|
||||
iterate(200);
|
||||
}
|
||||
|
||||
std::printf("toxes are online\n");
|
||||
@@ -289,27 +306,18 @@ void RecordBootstrap(const char *init, const char *bootstrap)
|
||||
|
||||
while (tox_friend_get_connection_status(tox2, friend_number, nullptr) == TOX_CONNECTION_NONE
|
||||
|| tox_friend_get_connection_status(tox1, 0, nullptr) == TOX_CONNECTION_NONE) {
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf("tox1: %d, tox2: %d, tox1 -> tox2: %d, tox2 -> tox1: %d\n",
|
||||
tox_self_get_connection_status(tox1), tox_self_get_connection_status(tox2),
|
||||
tox_friend_get_connection_status(tox1, 0, nullptr),
|
||||
tox_friend_get_connection_status(tox2, 0, nullptr));
|
||||
}
|
||||
iterate(System::BOOTSTRAP_ITERATION_INTERVAL);
|
||||
iterate(200);
|
||||
}
|
||||
|
||||
std::printf("tox clients connected\n");
|
||||
|
||||
dump(sys1->take_recording(), init);
|
||||
dump(recorder1.data, init);
|
||||
|
||||
// Clear the recorder.
|
||||
recorder1.data.clear();
|
||||
|
||||
while (state1.done < MESSAGE_COUNT && state2.done < MESSAGE_COUNT) {
|
||||
if (Fuzz_Data::FUZZ_DEBUG) {
|
||||
std::printf("tox1: %d, tox2: %d, tox1 -> tox2: %d, tox2 -> tox1: %d\n",
|
||||
tox_self_get_connection_status(tox1), tox_self_get_connection_status(tox2),
|
||||
tox_friend_get_connection_status(tox1, 0, nullptr),
|
||||
tox_friend_get_connection_status(tox2, 0, nullptr));
|
||||
}
|
||||
iterate(System::MESSAGE_ITERATION_INTERVAL);
|
||||
iterate(20);
|
||||
}
|
||||
|
||||
std::printf("test complete\n");
|
||||
@@ -318,7 +326,7 @@ void RecordBootstrap(const char *init, const char *bootstrap)
|
||||
tox_kill(tox2);
|
||||
tox_kill(tox1);
|
||||
|
||||
dump(sys1->recording(), bootstrap);
|
||||
dump(recorder1.data, bootstrap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
#include "../../toxcore/crypto_core.h"
|
||||
#include "../../toxcore/tox.h"
|
||||
#include "../../toxcore/tox_dispatch.h"
|
||||
#include "../../toxcore/tox_events.h"
|
||||
#include "../../toxcore/tox_private.h"
|
||||
#include "fuzz_support.hh"
|
||||
#include "fuzz_tox.hh"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr bool PROTODUMP_DEBUG = Fuzz_Data::FUZZ_DEBUG;
|
||||
|
||||
void setup_callbacks(Tox_Dispatch *dispatch)
|
||||
{
|
||||
tox_events_callback_conference_connected(
|
||||
dispatch, [](const Tox_Event_Conference_Connected *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_conference_connected(
|
||||
dispatch, [](const Tox_Event_Conference_Connected *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_conference_invite(
|
||||
dispatch, [](const Tox_Event_Conference_Invite *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_conference_message(
|
||||
dispatch, [](const Tox_Event_Conference_Message *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_conference_peer_list_changed(
|
||||
dispatch, [](const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_conference_peer_name(
|
||||
dispatch, [](const Tox_Event_Conference_Peer_Name *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_conference_title(dispatch,
|
||||
[](const Tox_Event_Conference_Title *event, void *user_data) { assert(event == nullptr); });
|
||||
tox_events_callback_file_chunk_request(
|
||||
dispatch, [](const Tox_Event_File_Chunk_Request *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_file_recv(dispatch,
|
||||
[](const Tox_Event_File_Recv *event, void *user_data) { assert(event == nullptr); });
|
||||
tox_events_callback_file_recv_chunk(dispatch,
|
||||
[](const Tox_Event_File_Recv_Chunk *event, void *user_data) { assert(event == nullptr); });
|
||||
tox_events_callback_file_recv_control(
|
||||
dispatch, [](const Tox_Event_File_Recv_Control *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_friend_connection_status(
|
||||
dispatch, [](const Tox_Event_Friend_Connection_Status *event, void *user_data) {
|
||||
Tox *tox = static_cast<Tox *>(user_data);
|
||||
// OK: friend came online.
|
||||
const uint32_t friend_number
|
||||
= tox_event_friend_connection_status_get_friend_number(event);
|
||||
assert(friend_number == 0);
|
||||
const uint8_t message = 'A';
|
||||
Tox_Err_Friend_Send_Message err;
|
||||
tox_friend_send_message(tox, friend_number, TOX_MESSAGE_TYPE_NORMAL, &message, 1, &err);
|
||||
assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK);
|
||||
});
|
||||
tox_events_callback_friend_lossless_packet(
|
||||
dispatch, [](const Tox_Event_Friend_Lossless_Packet *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_friend_lossy_packet(
|
||||
dispatch, [](const Tox_Event_Friend_Lossy_Packet *event, void *user_data) {
|
||||
assert(event == nullptr);
|
||||
});
|
||||
tox_events_callback_friend_message(
|
||||
dispatch, [](const Tox_Event_Friend_Message *event, void *user_data) {
|
||||
Tox *tox = static_cast<Tox *>(user_data);
|
||||
const uint32_t friend_number = tox_event_friend_message_get_friend_number(event);
|
||||
assert(friend_number == 0);
|
||||
const uint32_t message_length = tox_event_friend_message_get_message_length(event);
|
||||
assert(message_length == 1);
|
||||
const uint8_t *message = tox_event_friend_message_get_message(event);
|
||||
const uint8_t reply = message[0] + 1;
|
||||
Tox_Err_Friend_Send_Message err;
|
||||
tox_friend_send_message(tox, friend_number, TOX_MESSAGE_TYPE_NORMAL, &reply, 1, &err);
|
||||
assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK);
|
||||
});
|
||||
tox_events_callback_friend_name(
|
||||
dispatch, [](const Tox_Event_Friend_Name *event, void *user_data) {
|
||||
const uint32_t friend_number = tox_event_friend_name_get_friend_number(event);
|
||||
assert(friend_number == 0);
|
||||
});
|
||||
tox_events_callback_friend_read_receipt(
|
||||
dispatch, [](const Tox_Event_Friend_Read_Receipt *event, void *user_data) {
|
||||
const uint32_t friend_number = tox_event_friend_read_receipt_get_friend_number(event);
|
||||
assert(friend_number == 0);
|
||||
const uint32_t message_id = tox_event_friend_read_receipt_get_message_id(event);
|
||||
uint32_t *done = static_cast<uint32_t *>(user_data);
|
||||
*done = std::max(*done, message_id);
|
||||
});
|
||||
tox_events_callback_friend_request(
|
||||
dispatch, [](const Tox_Event_Friend_Request *event, void *user_data) {
|
||||
Tox *tox = static_cast<Tox *>(user_data);
|
||||
Tox_Err_Friend_Add err;
|
||||
tox_friend_add_norequest(tox, tox_event_friend_request_get_public_key(event), &err);
|
||||
});
|
||||
tox_events_callback_friend_status(
|
||||
dispatch, [](const Tox_Event_Friend_Status *event, void *user_data) {
|
||||
const uint32_t friend_number = tox_event_friend_status_get_friend_number(event);
|
||||
assert(friend_number == 0);
|
||||
});
|
||||
tox_events_callback_friend_status_message(
|
||||
dispatch, [](const Tox_Event_Friend_Status_Message *event, void *user_data) {
|
||||
const uint32_t friend_number = tox_event_friend_status_message_get_friend_number(event);
|
||||
assert(friend_number == 0);
|
||||
});
|
||||
tox_events_callback_friend_typing(
|
||||
dispatch, [](const Tox_Event_Friend_Typing *event, void *user_data) {
|
||||
const uint32_t friend_number = tox_event_friend_typing_get_friend_number(event);
|
||||
assert(friend_number == 0);
|
||||
assert(!tox_event_friend_typing_get_typing(event));
|
||||
});
|
||||
tox_events_callback_self_connection_status(
|
||||
dispatch, [](const Tox_Event_Self_Connection_Status *event, void *user_data) {
|
||||
// OK: we got connected.
|
||||
});
|
||||
}
|
||||
|
||||
void TestEndToEnd(Fuzz_Data &input)
|
||||
{
|
||||
/**
|
||||
* Whether to abort the program if a friend connection can be established.
|
||||
*
|
||||
* This is useful to make the fuzzer produce minimal startup data so the
|
||||
* interesting part of the fuzzer (the part that comes after the friend
|
||||
* connection is established) can run sooner and thus more frequently.
|
||||
*/
|
||||
const bool PROTODUMP_REDUCE = getenv("PROTODUMP_REDUCE") != nullptr;
|
||||
|
||||
Fuzz_System sys(input);
|
||||
|
||||
Ptr<Tox_Options> opts(tox_options_new(nullptr), tox_options_free);
|
||||
assert(opts != nullptr);
|
||||
tox_options_set_local_discovery_enabled(opts.get(), false);
|
||||
|
||||
Tox_Options_Testing tox_options_testing;
|
||||
tox_options_testing.operating_system = sys.sys.get();
|
||||
|
||||
tox_options_set_log_callback(opts.get(),
|
||||
[](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func,
|
||||
const char *message, void *user_data) {
|
||||
// Log to stdout.
|
||||
if (PROTODUMP_DEBUG) {
|
||||
std::printf("[tox1] %c %s:%d(%s): %s\n", tox_log_level_name(level), file, line,
|
||||
func, message);
|
||||
}
|
||||
});
|
||||
|
||||
Tox_Err_New error_new;
|
||||
Tox_Err_New_Testing error_new_testing;
|
||||
Tox *tox = tox_new_testing(opts.get(), &error_new, &tox_options_testing, &error_new_testing);
|
||||
|
||||
if (tox == nullptr) {
|
||||
// It might fail, because some I/O happens in tox_new, and the fuzzer
|
||||
// might do things that make that I/O fail.
|
||||
return;
|
||||
}
|
||||
|
||||
assert(error_new == TOX_ERR_NEW_OK);
|
||||
assert(error_new_testing == TOX_ERR_NEW_TESTING_OK);
|
||||
|
||||
tox_events_init(tox);
|
||||
|
||||
Tox_Dispatch *dispatch = tox_dispatch_new(nullptr);
|
||||
assert(dispatch != nullptr);
|
||||
setup_callbacks(dispatch);
|
||||
|
||||
while (!input.empty()) {
|
||||
Tox_Err_Events_Iterate error_iterate;
|
||||
Tox_Events *events = tox_events_iterate(tox, true, &error_iterate);
|
||||
tox_events_equal(tox_get_system(tox), events, events); // TODO(iphydf): assert?
|
||||
tox_dispatch_invoke(dispatch, events, tox);
|
||||
tox_events_free(events);
|
||||
const uint8_t clock_increment = random_u08(sys.rng.get());
|
||||
if (PROTODUMP_DEBUG) {
|
||||
printf("clock increment: %d\n", clock_increment);
|
||||
}
|
||||
sys.clock += std::max(System::MIN_ITERATION_INTERVAL, clock_increment);
|
||||
}
|
||||
|
||||
if (PROTODUMP_REDUCE) {
|
||||
assert(tox_friend_get_connection_status(tox, 0, nullptr) != 2);
|
||||
} else {
|
||||
printf("friend: %d\n", tox_friend_get_connection_status(tox, 0, nullptr));
|
||||
printf("self: %d\n", tox_self_get_connection_status(tox));
|
||||
assert(tox_friend_get_connection_status(tox, 0, nullptr) == 2);
|
||||
assert(input.empty());
|
||||
}
|
||||
|
||||
tox_dispatch_free(dispatch);
|
||||
tox_kill(tox);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
Fuzz_Data input{data, size};
|
||||
TestEndToEnd(input);
|
||||
return 0; // Non-zero return values are reserved for future use.
|
||||
}
|
||||
@@ -4,10 +4,14 @@
|
||||
|
||||
#include "../../toxcore/tox.h"
|
||||
#include "../../toxcore/tox_private.h"
|
||||
#include "fuzz_support.hh"
|
||||
#include "../support/public/fuzz_data.hh"
|
||||
#include "../support/public/simulated_environment.hh"
|
||||
|
||||
namespace {
|
||||
|
||||
using tox::test::Fuzz_Data;
|
||||
using tox::test::SimulatedEnvironment;
|
||||
|
||||
void TestSaveDataLoading(Fuzz_Data &input)
|
||||
{
|
||||
Tox_Err_Options_New error_options;
|
||||
@@ -27,10 +31,12 @@ void TestSaveDataLoading(Fuzz_Data &input)
|
||||
tox_options_set_savedata_type(tox_options, TOX_SAVEDATA_TYPE_TOX_SAVE);
|
||||
|
||||
Tox_Options_Testing tox_options_testing;
|
||||
Null_System sys;
|
||||
tox_options_testing.operating_system = sys.sys.get();
|
||||
SimulatedEnvironment env;
|
||||
auto node = env.create_node(33445);
|
||||
tox_options_testing.operating_system = &node->system;
|
||||
|
||||
Tox *tox = tox_new_testing(tox_options, nullptr, &tox_options_testing, nullptr);
|
||||
Tox_Err_New_Testing err_testing;
|
||||
Tox *tox = tox_new_testing(tox_options, nullptr, &tox_options_testing, &err_testing);
|
||||
tox_options_free(tox_options);
|
||||
if (tox == nullptr) {
|
||||
// Tox save was invalid, we're finished here
|
||||
|
||||
111
testing/support/BUILD.bazel
Normal file
111
testing/support/BUILD.bazel
Normal file
@@ -0,0 +1,111 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
|
||||
|
||||
cc_library(
|
||||
name = "support",
|
||||
srcs = [
|
||||
"doubles/fake_network_stack.cc",
|
||||
"doubles/fake_sockets.cc",
|
||||
"doubles/network_universe.cc",
|
||||
"src/clock.cc",
|
||||
"src/environment.cc",
|
||||
"src/fake_clock.cc",
|
||||
"src/fake_memory.cc",
|
||||
"src/fake_random.cc",
|
||||
"src/fuzz_helpers.cc",
|
||||
"src/memory.cc",
|
||||
"src/network.cc",
|
||||
"src/random.cc",
|
||||
"src/simulated_environment.cc",
|
||||
"src/simulation.cc",
|
||||
"src/tox_network.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"doubles/fake_clock.hh",
|
||||
"doubles/fake_memory.hh",
|
||||
"doubles/fake_network_stack.hh",
|
||||
"doubles/fake_random.hh",
|
||||
"doubles/fake_sockets.hh",
|
||||
"doubles/network_universe.hh",
|
||||
"public/clock.hh",
|
||||
"public/environment.hh",
|
||||
"public/fuzz_data.hh",
|
||||
"public/fuzz_helpers.hh",
|
||||
"public/memory.hh",
|
||||
"public/network.hh",
|
||||
"public/random.hh",
|
||||
"public/simulated_environment.hh",
|
||||
"public/simulation.hh",
|
||||
"public/tox_network.hh",
|
||||
],
|
||||
copts = select({
|
||||
"//tools/config:windows": ["/wd4200"], # Zero-sized array in struct/union
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//c-toxcore/toxcore:mem",
|
||||
"//c-toxcore/toxcore:network",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
"//c-toxcore/toxcore:tox_memory",
|
||||
"//c-toxcore/toxcore:tox_options",
|
||||
"//c-toxcore/toxcore:tox_random",
|
||||
"@psocket",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "fake_sockets_test",
|
||||
srcs = ["doubles/fake_sockets_test.cc"],
|
||||
deps = [
|
||||
":support",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
"@psocket",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "fake_network_stack_test",
|
||||
srcs = ["doubles/fake_network_stack_test.cc"],
|
||||
deps = [
|
||||
":support",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
"@psocket",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "network_universe_test",
|
||||
srcs = ["doubles/network_universe_test.cc"],
|
||||
deps = [
|
||||
":support",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
"@psocket",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "bootstrap_scaling_test",
|
||||
srcs = ["bootstrap_scaling_test.cc"],
|
||||
deps = [
|
||||
":support",
|
||||
"//c-toxcore/toxcore:network",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "tox_network_test",
|
||||
timeout = "long",
|
||||
srcs = ["tox_network_test.cc"],
|
||||
deps = [
|
||||
":support",
|
||||
"//c-toxcore/toxcore:tox",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
72
testing/support/CMakeLists.txt
Normal file
72
testing/support/CMakeLists.txt
Normal file
@@ -0,0 +1,72 @@
|
||||
if(NOT UNITTEST)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(support_SOURCES
|
||||
doubles/fake_network_stack.cc
|
||||
doubles/fake_sockets.cc
|
||||
doubles/network_universe.cc
|
||||
src/clock.cc
|
||||
src/environment.cc
|
||||
src/fake_clock.cc
|
||||
src/fake_memory.cc
|
||||
src/fake_random.cc
|
||||
src/fuzz_helpers.cc
|
||||
src/memory.cc
|
||||
src/network.cc
|
||||
src/random.cc
|
||||
src/simulated_environment.cc
|
||||
src/simulation.cc
|
||||
src/tox_network.cc
|
||||
doubles/fake_clock.hh
|
||||
doubles/fake_memory.hh
|
||||
doubles/fake_network_stack.hh
|
||||
doubles/fake_random.hh
|
||||
doubles/fake_sockets.hh
|
||||
doubles/network_universe.hh
|
||||
public/clock.hh
|
||||
public/environment.hh
|
||||
public/fuzz_data.hh
|
||||
public/fuzz_helpers.hh
|
||||
public/memory.hh
|
||||
public/network.hh
|
||||
public/random.hh
|
||||
public/simulated_environment.hh
|
||||
public/simulation.hh
|
||||
public/tox_network.hh
|
||||
)
|
||||
|
||||
add_library(support STATIC ${support_SOURCES})
|
||||
|
||||
if(TARGET toxcore_static)
|
||||
target_link_libraries(support PRIVATE toxcore_static)
|
||||
else()
|
||||
target_link_libraries(support PRIVATE toxcore_shared)
|
||||
endif()
|
||||
|
||||
if(TARGET pthreads4w::pthreads4w)
|
||||
target_link_libraries(support PUBLIC pthreads4w::pthreads4w)
|
||||
elseif(TARGET PThreads4W::PThreads4W)
|
||||
target_link_libraries(support PUBLIC PThreads4W::PThreads4W)
|
||||
elseif(TARGET Threads::Threads)
|
||||
target_link_libraries(support PUBLIC Threads::Threads)
|
||||
endif()
|
||||
|
||||
if(TARGET GTest::gtest_main)
|
||||
function(support_test target source)
|
||||
add_executable(${target} ${source})
|
||||
target_link_libraries(${target} PRIVATE support GTest::gtest_main)
|
||||
if(TARGET toxcore_static)
|
||||
target_link_libraries(${target} PRIVATE toxcore_static)
|
||||
else()
|
||||
target_link_libraries(${target} PRIVATE toxcore_shared)
|
||||
endif()
|
||||
add_test(NAME ${target} COMMAND ${target})
|
||||
endfunction()
|
||||
|
||||
support_test(fake_sockets_test doubles/fake_sockets_test.cc)
|
||||
support_test(fake_network_stack_test doubles/fake_network_stack_test.cc)
|
||||
support_test(network_universe_test doubles/network_universe_test.cc)
|
||||
support_test(bootstrap_scaling_test bootstrap_scaling_test.cc)
|
||||
support_test(tox_network_test tox_network_test.cc)
|
||||
endif()
|
||||
109
testing/support/bootstrap_scaling_test.cc
Normal file
109
testing/support/bootstrap_scaling_test.cc
Normal file
@@ -0,0 +1,109 @@
|
||||
// clang-format off
|
||||
#include "public/simulation.hh"
|
||||
// clang-format on
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "../../toxcore/network.h"
|
||||
#include "../../toxcore/tox.h"
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
class BootstrapScalingTest : public ::testing::Test {
|
||||
protected:
|
||||
Simulation sim;
|
||||
};
|
||||
|
||||
TEST_F(BootstrapScalingTest, TwentyNodes)
|
||||
{
|
||||
sim.net().set_verbose(false);
|
||||
const int num_nodes = 20;
|
||||
std::vector<std::unique_ptr<SimulatedNode>> nodes;
|
||||
std::vector<SimulatedNode::ToxPtr> toxes;
|
||||
|
||||
std::cerr << "[Test] Creating " << num_nodes << " nodes..." << std::endl;
|
||||
|
||||
// Create all nodes and tox instances
|
||||
for (int i = 0; i < num_nodes; ++i) {
|
||||
auto node = sim.create_node();
|
||||
auto tox = node->create_tox();
|
||||
ASSERT_NE(tox, nullptr) << "Failed to create Tox instance " << i;
|
||||
nodes.push_back(std::move(node));
|
||||
toxes.push_back(std::move(tox));
|
||||
}
|
||||
|
||||
// Node 0 is the bootstrap target
|
||||
uint8_t bootstrap_dht_id[TOX_PUBLIC_KEY_SIZE];
|
||||
tox_self_get_dht_id(toxes[0].get(), bootstrap_dht_id);
|
||||
|
||||
FakeUdpSocket *main_sock = nodes[0]->get_primary_socket();
|
||||
ASSERT_NE(main_sock, nullptr) << "Node 0 has no primary socket";
|
||||
uint16_t bootstrap_port = main_sock->local_port();
|
||||
|
||||
char bootstrap_ip[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&nodes[0]->ip, bootstrap_ip, sizeof(bootstrap_ip));
|
||||
|
||||
std::cerr << "[Test] Bootstrapping to Node 0 at " << bootstrap_ip << ":" << bootstrap_port
|
||||
<< std::endl;
|
||||
|
||||
size_t total_packets = 0;
|
||||
sim.net().add_observer([&](const Packet &) { total_packets++; });
|
||||
|
||||
// Everyone else bootstraps to Node 0
|
||||
for (int i = 1; i < num_nodes; ++i) {
|
||||
Tox_Err_Bootstrap err;
|
||||
bool success = tox_bootstrap(
|
||||
toxes[i].get(), bootstrap_ip, bootstrap_port, bootstrap_dht_id, &err);
|
||||
ASSERT_TRUE(success) << "Bootstrap call failed for node " << i << ": " << err;
|
||||
}
|
||||
|
||||
// Run simulation until everyone is connected or timeout
|
||||
const uint64_t virtual_timeout_ms = 300000;
|
||||
bool all_connected = false;
|
||||
|
||||
sim.run_until(
|
||||
[&]() {
|
||||
all_connected = true;
|
||||
size_t connected_count = 0;
|
||||
for (int i = 0; i < num_nodes; ++i) {
|
||||
tox_iterate(toxes[i].get(), nullptr);
|
||||
if (tox_self_get_connection_status(toxes[i].get()) != TOX_CONNECTION_NONE) {
|
||||
connected_count++;
|
||||
} else {
|
||||
all_connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t last_print = 0;
|
||||
if (sim.clock().current_time_ms() - last_print > 5000) {
|
||||
std::cerr << "[Test] DHT connected: " << connected_count << "/" << num_nodes
|
||||
<< " at " << sim.clock().current_time_ms()
|
||||
<< "ms. Total packets: " << total_packets << std::endl;
|
||||
last_print = sim.clock().current_time_ms();
|
||||
}
|
||||
|
||||
return all_connected;
|
||||
},
|
||||
virtual_timeout_ms);
|
||||
|
||||
if (!all_connected) {
|
||||
std::cerr << "[Test] Bootstrap failed. Status of nodes:" << std::endl;
|
||||
for (int i = 0; i < num_nodes; ++i) {
|
||||
Tox_Connection status = tox_self_get_connection_status(toxes[i].get());
|
||||
if (status == TOX_CONNECTION_NONE) {
|
||||
std::cerr << " Node " << i << " is NOT connected." << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(all_connected)
|
||||
<< "Only " << (num_nodes - (all_connected ? 0 : 1)) << " nodes connected? Check logs.";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
23
testing/support/doubles/fake_clock.hh
Normal file
23
testing/support/doubles/fake_clock.hh
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
|
||||
|
||||
#include "../public/clock.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeClock : public ClockSystem {
|
||||
public:
|
||||
explicit FakeClock(uint64_t start_time_ms = 1000);
|
||||
|
||||
uint64_t current_time_ms() const override;
|
||||
uint64_t current_time_s() const override;
|
||||
|
||||
void advance(uint64_t ms);
|
||||
|
||||
private:
|
||||
uint64_t now_ms_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
|
||||
51
testing/support/doubles/fake_memory.hh
Normal file
51
testing/support/doubles/fake_memory.hh
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "../public/memory.hh"
|
||||
|
||||
// Forward declaration
|
||||
struct Tox_Memory;
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeMemory : public MemorySystem {
|
||||
public:
|
||||
using FailureInjector = std::function<bool(size_t size)>; // Return true to fail
|
||||
using Observer = std::function<void(bool success)>;
|
||||
|
||||
FakeMemory();
|
||||
~FakeMemory() override;
|
||||
|
||||
void *malloc(size_t size) override;
|
||||
void *realloc(void *ptr, size_t size) override;
|
||||
void free(void *ptr) override;
|
||||
|
||||
// Configure failure injection
|
||||
void set_failure_injector(FailureInjector injector);
|
||||
|
||||
// Configure observer
|
||||
void set_observer(Observer observer);
|
||||
|
||||
// Get the C-compatible struct
|
||||
struct Tox_Memory get_c_memory();
|
||||
|
||||
private:
|
||||
struct Header {
|
||||
size_t size;
|
||||
size_t magic;
|
||||
};
|
||||
static constexpr size_t kMagic = 0xDEADC0DE;
|
||||
static constexpr size_t kFreeMagic = 0xBAADF00D;
|
||||
|
||||
size_t current_allocation_ = 0;
|
||||
size_t max_allocation_ = 0;
|
||||
|
||||
FailureInjector failure_injector_;
|
||||
Observer observer_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
|
||||
299
testing/support/doubles/fake_network_stack.cc
Normal file
299
testing/support/doubles/fake_network_stack.cc
Normal file
@@ -0,0 +1,299 @@
|
||||
#include "fake_network_stack.hh"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../../toxcore/mem.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
static const Network_Funcs kVtable = {
|
||||
.close
|
||||
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->close(sock); },
|
||||
.accept
|
||||
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->accept(sock); },
|
||||
.bind
|
||||
= [](void *obj, Socket sock,
|
||||
const IP_Port *addr) { return static_cast<FakeNetworkStack *>(obj)->bind(sock, addr); },
|
||||
.listen
|
||||
= [](void *obj, Socket sock,
|
||||
int backlog) { return static_cast<FakeNetworkStack *>(obj)->listen(sock, backlog); },
|
||||
.connect =
|
||||
[](void *obj, Socket sock, const IP_Port *addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->connect(sock, addr);
|
||||
},
|
||||
.recvbuf
|
||||
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->recvbuf(sock); },
|
||||
.recv = [](void *obj, Socket sock, uint8_t *buf,
|
||||
size_t len) { return static_cast<FakeNetworkStack *>(obj)->recv(sock, buf, len); },
|
||||
.recvfrom =
|
||||
[](void *obj, Socket sock, uint8_t *buf, size_t len, IP_Port *addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->recvfrom(sock, buf, len, addr);
|
||||
},
|
||||
.send = [](void *obj, Socket sock, const uint8_t *buf,
|
||||
size_t len) { return static_cast<FakeNetworkStack *>(obj)->send(sock, buf, len); },
|
||||
.sendto =
|
||||
[](void *obj, Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->sendto(sock, buf, len, addr);
|
||||
},
|
||||
.socket
|
||||
= [](void *obj, int domain, int type,
|
||||
int proto) { return static_cast<FakeNetworkStack *>(obj)->socket(domain, type, proto); },
|
||||
.socket_nonblock =
|
||||
[](void *obj, Socket sock, bool nonblock) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->socket_nonblock(sock, nonblock);
|
||||
},
|
||||
.getsockopt =
|
||||
[](void *obj, Socket sock, int level, int optname, void *optval, size_t *optlen) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->getsockopt(
|
||||
sock, level, optname, optval, optlen);
|
||||
},
|
||||
.setsockopt =
|
||||
[](void *obj, Socket sock, int level, int optname, const void *optval, size_t optlen) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->setsockopt(
|
||||
sock, level, optname, optval, optlen);
|
||||
},
|
||||
.getaddrinfo =
|
||||
[](void *obj, const Memory *mem, const char *address, int family, int protocol,
|
||||
IP_Port **addrs) {
|
||||
FakeNetworkStack *self = static_cast<FakeNetworkStack *>(obj);
|
||||
if (self->universe().is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] getaddrinfo for " << address << std::endl;
|
||||
}
|
||||
if (strcmp(address, "127.0.0.1") == 0 || strcmp(address, "localhost") == 0) {
|
||||
*addrs = static_cast<IP_Port *>(mem_alloc(mem, sizeof(IP_Port)));
|
||||
memset(&(*addrs)->ip, 0, sizeof(IP));
|
||||
ip_init(&(*addrs)->ip, false);
|
||||
(*addrs)->ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
(*addrs)->port = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
IP ip;
|
||||
if (addr_parse_ip(address, &ip)) {
|
||||
*addrs = static_cast<IP_Port *>(mem_alloc(mem, sizeof(IP_Port)));
|
||||
(*addrs)->ip = ip;
|
||||
(*addrs)->port = 0;
|
||||
if (self->universe().is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] resolved " << address << std::endl;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
.freeaddrinfo =
|
||||
[](void *obj, const Memory *mem, IP_Port *addrs) {
|
||||
mem_delete(mem, addrs);
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
|
||||
FakeNetworkStack::FakeNetworkStack(NetworkUniverse &universe, const IP &node_ip)
|
||||
: universe_(universe)
|
||||
, node_ip_(node_ip)
|
||||
{
|
||||
}
|
||||
|
||||
FakeNetworkStack::~FakeNetworkStack() = default;
|
||||
|
||||
struct Network FakeNetworkStack::get_c_network() { return Network{&kVtable, this}; }
|
||||
|
||||
Socket FakeNetworkStack::socket(int domain, int type, int protocol)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
int fd = next_fd_++;
|
||||
|
||||
std::unique_ptr<FakeSocket> sock;
|
||||
if (type == SOCK_DGRAM) {
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] create UDP socket fd=" << fd << std::endl;
|
||||
}
|
||||
sock = std::make_unique<FakeUdpSocket>(universe_);
|
||||
} else if (type == SOCK_STREAM) {
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] create TCP socket fd=" << fd << std::endl;
|
||||
}
|
||||
sock = std::make_unique<FakeTcpSocket>(universe_);
|
||||
} else {
|
||||
// Unknown type
|
||||
return net_socket_from_native(-1);
|
||||
}
|
||||
|
||||
sockets_[fd] = std::move(sock);
|
||||
sockets_[fd]->set_ip(node_ip_);
|
||||
return net_socket_from_native(fd);
|
||||
}
|
||||
|
||||
FakeSocket *FakeNetworkStack::get_sock(Socket sock)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto it = sockets_.find(net_socket_to_native(sock));
|
||||
if (it != sockets_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::close(Socket sock)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
int fd = net_socket_to_native(sock);
|
||||
auto it = sockets_.find(fd);
|
||||
if (it == sockets_.end()) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
it->second->close();
|
||||
sockets_.erase(it);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Delegate all others
|
||||
int FakeNetworkStack::bind(Socket sock, const IP_Port *addr)
|
||||
{
|
||||
if (auto *s = get_sock(sock)) {
|
||||
int ret = s->bind(addr);
|
||||
if (universe_.is_verbose() && ret == 0) {
|
||||
char ip_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&s->ip_address(), ip_str, sizeof(ip_str));
|
||||
std::cerr << "[FakeNetworkStack] bound socket to " << ip_str << ":" << s->local_port()
|
||||
<< std::endl;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::connect(Socket sock, const IP_Port *addr)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->connect(addr);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::listen(Socket sock, int backlog)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->listen(backlog);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Socket FakeNetworkStack::accept(Socket sock)
|
||||
{
|
||||
// This requires creating a new FD
|
||||
IP_Port addr;
|
||||
std::unique_ptr<FakeSocket> new_sock_obj;
|
||||
|
||||
{
|
||||
auto *s = get_sock(sock);
|
||||
if (!s) {
|
||||
errno = EBADF;
|
||||
return net_socket_from_native(-1);
|
||||
}
|
||||
new_sock_obj = s->accept(&addr);
|
||||
}
|
||||
|
||||
if (!new_sock_obj) {
|
||||
// errno set by accept
|
||||
return net_socket_from_native(-1);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
int fd = next_fd_++;
|
||||
sockets_[fd] = std::move(new_sock_obj);
|
||||
return net_socket_from_native(fd);
|
||||
}
|
||||
|
||||
int FakeNetworkStack::send(Socket sock, const uint8_t *buf, size_t len)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->send(buf, len);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::recv(Socket sock, uint8_t *buf, size_t len)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->recv(buf, len);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::recvbuf(Socket sock)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->recv_buffer_size();
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->sendto(buf, len, addr);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->recvfrom(buf, len, addr);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::socket_nonblock(Socket sock, bool nonblock)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->socket_nonblock(nonblock);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->getsockopt(level, optname, optval, optlen);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::setsockopt(
|
||||
Socket sock, int level, int optname, const void *optval, size_t optlen)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->setsockopt(level, optname, optval, optlen);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
FakeUdpSocket *FakeNetworkStack::get_udp_socket(Socket sock)
|
||||
{
|
||||
if (auto *s = get_sock(sock)) {
|
||||
if (s->type() == SOCK_DGRAM) {
|
||||
return static_cast<FakeUdpSocket *>(s);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<FakeUdpSocket *> FakeNetworkStack::get_bound_udp_sockets()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<FakeUdpSocket *> result;
|
||||
for (const auto &pair : sockets_) {
|
||||
FakeSocket *s = pair.second.get();
|
||||
if (s->type() == SOCK_DGRAM && s->local_port() != 0) {
|
||||
result.push_back(static_cast<FakeUdpSocket *>(s));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
56
testing/support/doubles/fake_network_stack.hh
Normal file
56
testing/support/doubles/fake_network_stack.hh
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "../public/network.hh"
|
||||
#include "fake_sockets.hh"
|
||||
#include "network_universe.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeNetworkStack : public NetworkSystem {
|
||||
public:
|
||||
explicit FakeNetworkStack(NetworkUniverse &universe, const IP &node_ip);
|
||||
~FakeNetworkStack() override;
|
||||
|
||||
// NetworkSystem Implementation
|
||||
Socket socket(int domain, int type, int protocol) override;
|
||||
int bind(Socket sock, const IP_Port *addr) override;
|
||||
int close(Socket sock) override;
|
||||
int sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) override;
|
||||
int recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr) override;
|
||||
|
||||
int listen(Socket sock, int backlog) override;
|
||||
Socket accept(Socket sock) override;
|
||||
int connect(Socket sock, const IP_Port *addr) override;
|
||||
int send(Socket sock, const uint8_t *buf, size_t len) override;
|
||||
int recv(Socket sock, uint8_t *buf, size_t len) override;
|
||||
int recvbuf(Socket sock) override;
|
||||
|
||||
int socket_nonblock(Socket sock, bool nonblock) override;
|
||||
int getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen) override;
|
||||
int setsockopt(Socket sock, int level, int optname, const void *optval, size_t optlen) override;
|
||||
|
||||
struct Network get_c_network();
|
||||
|
||||
// For testing/fuzzing introspection
|
||||
FakeUdpSocket *get_udp_socket(Socket sock);
|
||||
std::vector<FakeUdpSocket *> get_bound_udp_sockets();
|
||||
|
||||
NetworkUniverse &universe() { return universe_; }
|
||||
|
||||
private:
|
||||
FakeSocket *get_sock(Socket sock);
|
||||
|
||||
NetworkUniverse &universe_;
|
||||
std::map<int, std::unique_ptr<FakeSocket>> sockets_;
|
||||
int next_fd_ = 100;
|
||||
IP node_ip_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H
|
||||
91
testing/support/doubles/fake_network_stack_test.cc
Normal file
91
testing/support/doubles/fake_network_stack_test.cc
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "fake_network_stack.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "network_universe.hh"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
class FakeNetworkStackTest : public ::testing::Test {
|
||||
public:
|
||||
FakeNetworkStackTest()
|
||||
: stack{universe, make_ip(0x7F000001)}
|
||||
{
|
||||
}
|
||||
~FakeNetworkStackTest() override;
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
FakeNetworkStack stack;
|
||||
};
|
||||
|
||||
FakeNetworkStackTest::~FakeNetworkStackTest() = default;
|
||||
|
||||
TEST_F(FakeNetworkStackTest, SocketCreationAndLifecycle)
|
||||
{
|
||||
Socket udp_sock = stack.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
ASSERT_NE(net_socket_to_native(udp_sock), -1);
|
||||
|
||||
// Check introspection
|
||||
ASSERT_NE(stack.get_udp_socket(udp_sock), nullptr);
|
||||
|
||||
// Bind
|
||||
IP_Port addr;
|
||||
ip_init(&addr.ip, false);
|
||||
addr.ip.ip.v4.uint32 = 0;
|
||||
addr.port = net_htons(9002);
|
||||
ASSERT_EQ(stack.bind(udp_sock, &addr), 0);
|
||||
|
||||
// Check introspection again
|
||||
auto sockets = stack.get_bound_udp_sockets();
|
||||
ASSERT_EQ(sockets.size(), 1);
|
||||
EXPECT_EQ(sockets[0]->local_port(), 9002);
|
||||
|
||||
ASSERT_EQ(stack.close(udp_sock), 0);
|
||||
ASSERT_EQ(stack.get_bound_udp_sockets().size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkStackTest, TcpSocketThroughStack)
|
||||
{
|
||||
Socket tcp_sock = stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
ASSERT_NE(net_socket_to_native(tcp_sock), -1);
|
||||
|
||||
IP_Port addr;
|
||||
ip_init(&addr.ip, false);
|
||||
addr.ip.ip.v4.uint32 = 0;
|
||||
addr.port = net_htons(9003);
|
||||
ASSERT_EQ(stack.bind(tcp_sock, &addr), 0);
|
||||
ASSERT_EQ(stack.listen(tcp_sock, 5), 0);
|
||||
|
||||
// Connect from another stack
|
||||
FakeNetworkStack client_stack{universe, make_ip(0x7F000002)};
|
||||
Socket client_sock = client_stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); // Localhost
|
||||
server_addr.port = net_htons(9003);
|
||||
|
||||
ASSERT_EQ(client_stack.connect(client_sock, &server_addr), -1);
|
||||
ASSERT_EQ(errno, EINPROGRESS);
|
||||
|
||||
universe.process_events(0); // SYN
|
||||
universe.process_events(0); // SYN-ACK
|
||||
universe.process_events(0); // ACK
|
||||
|
||||
Socket accepted = stack.accept(tcp_sock);
|
||||
ASSERT_NE(net_socket_to_native(accepted), -1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
45
testing/support/doubles/fake_random.hh
Normal file
45
testing/support/doubles/fake_random.hh
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H
|
||||
|
||||
#include <functional>
|
||||
#include <random>
|
||||
|
||||
#include "../public/random.hh"
|
||||
|
||||
// Forward declaration
|
||||
struct Tox_Random;
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeRandom : public RandomSystem {
|
||||
public:
|
||||
using EntropySource = std::function<void(uint8_t *out, size_t count)>;
|
||||
using Observer = std::function<void(const uint8_t *data, size_t count)>;
|
||||
|
||||
explicit FakeRandom(uint64_t seed);
|
||||
|
||||
uint32_t uniform(uint32_t upper_bound) override;
|
||||
void bytes(uint8_t *out, size_t count) override;
|
||||
|
||||
/**
|
||||
* @brief Set a custom entropy source.
|
||||
* If set, this function will be called to generate random bytes instead of the PRNG.
|
||||
*/
|
||||
void set_entropy_source(EntropySource source);
|
||||
|
||||
/**
|
||||
* @brief Set an observer to record generated bytes.
|
||||
*/
|
||||
void set_observer(Observer observer);
|
||||
|
||||
struct Tox_Random get_c_random();
|
||||
|
||||
private:
|
||||
std::minstd_rand rng_;
|
||||
EntropySource entropy_source_;
|
||||
Observer observer_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H
|
||||
485
testing/support/doubles/fake_sockets.cc
Normal file
485
testing/support/doubles/fake_sockets.cc
Normal file
@@ -0,0 +1,485 @@
|
||||
#include "fake_sockets.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "network_universe.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
// --- FakeSocket ---
|
||||
|
||||
FakeSocket::FakeSocket(NetworkUniverse &universe, int type)
|
||||
: universe_(universe)
|
||||
, type_(type)
|
||||
{
|
||||
ip_init(&ip_, false);
|
||||
ip_.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
}
|
||||
|
||||
FakeSocket::~FakeSocket() = default;
|
||||
|
||||
int FakeSocket::close()
|
||||
{
|
||||
// Override in subclasses to unbind
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FakeSocket::getsockopt(int level, int optname, void *optval, size_t *optlen) { return 0; }
|
||||
int FakeSocket::setsockopt(int level, int optname, const void *optval, size_t optlen) { return 0; }
|
||||
int FakeSocket::socket_nonblock(bool nonblock)
|
||||
{
|
||||
nonblocking_ = nonblock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- FakeUdpSocket ---
|
||||
|
||||
FakeUdpSocket::FakeUdpSocket(NetworkUniverse &universe)
|
||||
: FakeSocket(universe, SOCK_DGRAM)
|
||||
{
|
||||
}
|
||||
|
||||
FakeUdpSocket::~FakeUdpSocket() { close_impl(); }
|
||||
|
||||
int FakeUdpSocket::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
close_impl();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FakeUdpSocket::close_impl()
|
||||
{
|
||||
if (local_port_ != 0) {
|
||||
universe_.unbind_udp(ip_, local_port_);
|
||||
local_port_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int FakeUdpSocket::bind(const IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ != 0)
|
||||
return -1; // Already bound
|
||||
|
||||
uint16_t port = addr->port;
|
||||
if (port == 0) {
|
||||
port = universe_.find_free_port(ip_);
|
||||
} else {
|
||||
port = net_ntohs(port);
|
||||
}
|
||||
|
||||
if (universe_.bind_udp(ip_, port, this)) {
|
||||
local_port_ = port;
|
||||
return 0;
|
||||
}
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::connect(const IP_Port *addr)
|
||||
{
|
||||
// UDP connect just sets default dest.
|
||||
// Not strictly needed for toxcore UDP but good for completeness.
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::listen(int backlog)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
std::unique_ptr<FakeSocket> FakeUdpSocket::accept(IP_Port *addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return nullptr;
|
||||
}
|
||||
int FakeUdpSocket::send(const uint8_t *buf, size_t len)
|
||||
{
|
||||
errno = EDESTADDRREQ;
|
||||
return -1;
|
||||
}
|
||||
int FakeUdpSocket::recv(uint8_t *buf, size_t len)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ == 0) {
|
||||
// Implicit bind
|
||||
uint16_t p = universe_.find_free_port(ip_);
|
||||
if (universe_.bind_udp(ip_, p, this)) {
|
||||
local_port_ = p;
|
||||
} else {
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Packet p{};
|
||||
// Source
|
||||
p.from.ip = ip_;
|
||||
p.from.port = net_htons(local_port_);
|
||||
p.to = *addr;
|
||||
p.data.assign(buf, buf + len);
|
||||
p.is_tcp = false;
|
||||
|
||||
universe_.send_packet(p);
|
||||
if (universe_.is_verbose()) {
|
||||
uint32_t tip4 = net_ntohl(addr->ip.ip.v4.uint32);
|
||||
std::cerr << "[FakeUdpSocket] sent " << len << " bytes from port " << local_port_ << " to "
|
||||
<< ((tip4 >> 24) & 0xFF) << "." << ((tip4 >> 16) & 0xFF) << "."
|
||||
<< ((tip4 >> 8) & 0xFF) << "." << (tip4 & 0xFF) << ":" << net_ntohs(addr->port)
|
||||
<< std::endl;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr)
|
||||
{
|
||||
RecvObserver observer_copy;
|
||||
std::vector<uint8_t> data_copy;
|
||||
IP_Port from_copy;
|
||||
size_t copy_len = 0;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (recv_queue_.empty() && packet_source_) {
|
||||
// NOTE: We call packet_source_ with lock held.
|
||||
// Be careful not to call back into socket methods from packet_source_.
|
||||
std::vector<uint8_t> data;
|
||||
IP_Port from;
|
||||
if (packet_source_(data, from)) {
|
||||
recv_queue_.push_back({std::move(data), from});
|
||||
}
|
||||
}
|
||||
|
||||
if (recv_queue_.empty()) {
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto &p = recv_queue_.front();
|
||||
copy_len = std::min(len, p.data.size());
|
||||
std::memcpy(buf, p.data.data(), copy_len);
|
||||
*addr = p.from;
|
||||
|
||||
if (recv_observer_) {
|
||||
observer_copy = recv_observer_;
|
||||
data_copy = p.data;
|
||||
from_copy = p.from;
|
||||
}
|
||||
|
||||
recv_queue_.pop_front();
|
||||
}
|
||||
|
||||
if (observer_copy) {
|
||||
observer_copy(data_copy, from_copy);
|
||||
}
|
||||
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeUdpSocket] recv " << copy_len << " bytes at port " << local_port_
|
||||
<< " from port " << net_ntohs(addr->port) << std::endl;
|
||||
}
|
||||
|
||||
return copy_len;
|
||||
}
|
||||
|
||||
void FakeUdpSocket::push_packet(std::vector<uint8_t> data, IP_Port from)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (universe_.is_verbose()) {
|
||||
uint32_t fip4 = net_ntohl(from.ip.ip.v4.uint32);
|
||||
std::cerr << "[FakeUdpSocket] push " << data.size() << " bytes into queue for "
|
||||
<< ((ip_.ip.v4.uint32 >> 24) & 0xFF)
|
||||
<< "." // ip_ is in network order from net_htonl
|
||||
<< ((ip_.ip.v4.uint32 >> 16) & 0xFF) << "." << ((ip_.ip.v4.uint32 >> 8) & 0xFF)
|
||||
<< "." << (ip_.ip.v4.uint32 & 0xFF) << ":" << local_port_ << " from "
|
||||
<< ((fip4 >> 24) & 0xFF) << "." << ((fip4 >> 16) & 0xFF) << "."
|
||||
<< ((fip4 >> 8) & 0xFF) << "." << (fip4 & 0xFF) << ":" << net_ntohs(from.port)
|
||||
<< std::endl;
|
||||
}
|
||||
recv_queue_.push_back({std::move(data), from});
|
||||
}
|
||||
|
||||
void FakeUdpSocket::set_packet_source(PacketSource source)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
packet_source_ = std::move(source);
|
||||
}
|
||||
|
||||
void FakeUdpSocket::set_recv_observer(RecvObserver observer)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
recv_observer_ = std::move(observer);
|
||||
}
|
||||
|
||||
// --- FakeTcpSocket ---
|
||||
|
||||
FakeTcpSocket::FakeTcpSocket(NetworkUniverse &universe)
|
||||
: FakeSocket(universe, SOCK_STREAM)
|
||||
, remote_addr_{}
|
||||
{
|
||||
}
|
||||
|
||||
FakeTcpSocket::~FakeTcpSocket() { close_impl(); }
|
||||
|
||||
int FakeTcpSocket::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
close_impl();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FakeTcpSocket::close_impl()
|
||||
{
|
||||
if (local_port_ != 0) {
|
||||
universe_.unbind_tcp(ip_, local_port_, this);
|
||||
local_port_ = 0;
|
||||
}
|
||||
state_ = CLOSED;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::bind(const IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ != 0)
|
||||
return -1;
|
||||
|
||||
uint16_t port = addr->port;
|
||||
if (port == 0) {
|
||||
port = universe_.find_free_port(ip_);
|
||||
} else {
|
||||
port = net_ntohs(port);
|
||||
}
|
||||
|
||||
if (universe_.bind_tcp(ip_, port, this)) {
|
||||
local_port_ = port;
|
||||
return 0;
|
||||
}
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::listen(int backlog)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
state_ = LISTEN;
|
||||
backlog_ = backlog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::connect(const IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ == 0) {
|
||||
// Implicit bind
|
||||
uint16_t p = universe_.find_free_port(ip_);
|
||||
if (universe_.bind_tcp(ip_, p, this)) {
|
||||
local_port_ = p;
|
||||
} else {
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
remote_addr_ = *addr;
|
||||
state_ = SYN_SENT;
|
||||
|
||||
Packet p{};
|
||||
p.from.ip = ip_;
|
||||
p.from.port = net_htons(local_port_);
|
||||
p.to = *addr;
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x02; // SYN
|
||||
p.seq = next_seq_;
|
||||
|
||||
universe_.send_packet(p);
|
||||
|
||||
// Non-blocking connect not fully simulated (we return 0 but state is SYN_SENT).
|
||||
// Real connect() blocks or returns EINPROGRESS.
|
||||
// For simplicity, we assume the test will pump events until connected.
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != LISTEN) {
|
||||
errno = EINVAL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pending_connections_.empty()) {
|
||||
errno = EWOULDBLOCK;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto client = std::move(pending_connections_.front());
|
||||
pending_connections_.pop_front();
|
||||
|
||||
if (addr) {
|
||||
*addr = client->remote_addr();
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::send(const uint8_t *buf, size_t len)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != ESTABLISHED) {
|
||||
errno = ENOTCONN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Wrap as TCP packet
|
||||
Packet p{};
|
||||
// Source
|
||||
p.from.ip = ip_;
|
||||
p.from.port = net_htons(local_port_);
|
||||
p.to = remote_addr_;
|
||||
p.data.assign(buf, buf + len);
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x10; // ACK (Data packets usually have ACK)
|
||||
p.seq = next_seq_;
|
||||
p.ack = last_ack_;
|
||||
|
||||
next_seq_ += len;
|
||||
universe_.send_packet(p);
|
||||
return len;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::recv(uint8_t *buf, size_t len)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (recv_buffer_.empty()) {
|
||||
if (state_ == CLOSED || state_ == CLOSE_WAIT)
|
||||
return 0; // EOF
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t actual = std::min(len, recv_buffer_.size());
|
||||
for (size_t i = 0; i < actual; ++i) {
|
||||
buf[i] = recv_buffer_.front();
|
||||
recv_buffer_.pop_front();
|
||||
}
|
||||
return actual;
|
||||
}
|
||||
|
||||
size_t FakeTcpSocket::recv_buffer_size()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return recv_buffer_.size();
|
||||
}
|
||||
|
||||
int FakeTcpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
int FakeTcpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void FakeTcpSocket::handle_packet(const Packet &p)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "Handle Packet: Port " << local_port_ << " Flags "
|
||||
<< static_cast<int>(p.tcp_flags) << " State " << state_ << std::endl;
|
||||
}
|
||||
|
||||
if (state_ == LISTEN) {
|
||||
if (p.tcp_flags & 0x02) { // SYN
|
||||
// Create new socket for connection
|
||||
auto new_sock = std::make_unique<FakeTcpSocket>(universe_);
|
||||
// Bind to ephemeral? No, it's accepted on the same port but distinct 4-tuple.
|
||||
// In our simplified model, the new socket is not bound to the global map
|
||||
// until accepted? Or effectively bound to the 4-tuple.
|
||||
// For now, let's just create it and queue it.
|
||||
|
||||
new_sock->state_ = SYN_RECEIVED;
|
||||
new_sock->remote_addr_ = p.from;
|
||||
new_sock->local_port_ = local_port_;
|
||||
new_sock->last_ack_ = p.seq + 1;
|
||||
new_sock->next_seq_ = 1000; // Random ISN
|
||||
|
||||
universe_.bind_tcp(ip_, local_port_, new_sock.get());
|
||||
|
||||
// Send SYN-ACK
|
||||
Packet resp{};
|
||||
resp.from = p.to;
|
||||
resp.to = p.from;
|
||||
resp.is_tcp = true;
|
||||
resp.tcp_flags = 0x12; // SYN | ACK
|
||||
resp.seq = new_sock->next_seq_++;
|
||||
resp.ack = new_sock->last_ack_;
|
||||
|
||||
universe_.send_packet(resp);
|
||||
|
||||
// In real TCP, we wait for ACK to move to ESTABLISHED and accept queue.
|
||||
// Here we cheat and move to ESTABLISHED immediately or wait for ACK?
|
||||
// Let's wait for ACK.
|
||||
// But where do we store this half-open socket?
|
||||
// For simplicity: auto-transition to ESTABLISHED and queue it.
|
||||
new_sock->state_ = ESTABLISHED;
|
||||
pending_connections_.push_back(std::move(new_sock));
|
||||
}
|
||||
} else if (state_ == SYN_SENT) {
|
||||
if ((p.tcp_flags & 0x12) == 0x12) { // SYN | ACK
|
||||
state_ = ESTABLISHED;
|
||||
last_ack_ = p.seq + 1;
|
||||
next_seq_++; // Consumer SYN
|
||||
|
||||
// Send ACK
|
||||
Packet ack{};
|
||||
ack.from = p.to;
|
||||
ack.to = p.from;
|
||||
ack.is_tcp = true;
|
||||
ack.tcp_flags = 0x10; // ACK
|
||||
ack.seq = next_seq_;
|
||||
ack.ack = last_ack_;
|
||||
universe_.send_packet(ack);
|
||||
}
|
||||
} else if (state_ == ESTABLISHED) {
|
||||
if (p.tcp_flags & 0x01) { // FIN
|
||||
state_ = CLOSE_WAIT;
|
||||
// Send ACK
|
||||
Packet ack{};
|
||||
ack.from = p.to;
|
||||
ack.to = p.from;
|
||||
ack.is_tcp = true;
|
||||
ack.tcp_flags = 0x10; // ACK
|
||||
ack.seq = next_seq_;
|
||||
ack.ack = p.seq + 1; // Consume FIN
|
||||
universe_.send_packet(ack);
|
||||
} else if (!p.data.empty()) {
|
||||
recv_buffer_.insert(recv_buffer_.end(), p.data.begin(), p.data.end());
|
||||
last_ack_ += p.data.size();
|
||||
// Should send ACK?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeTcpSocket> FakeTcpSocket::create_connected(
|
||||
NetworkUniverse &universe, const IP_Port &remote, uint16_t local_port)
|
||||
{
|
||||
auto s = std::make_unique<FakeTcpSocket>(universe);
|
||||
s->state_ = ESTABLISHED;
|
||||
s->remote_addr_ = remote;
|
||||
s->local_port_ = local_port;
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
165
testing/support/doubles/fake_sockets.hh
Normal file
165
testing/support/doubles/fake_sockets.hh
Normal file
@@ -0,0 +1,165 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include "../../../toxcore/network.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class NetworkUniverse;
|
||||
struct Packet;
|
||||
|
||||
/**
|
||||
* @brief Abstract base class for all fake sockets.
|
||||
*/
|
||||
class FakeSocket {
|
||||
public:
|
||||
FakeSocket(NetworkUniverse &universe, int type);
|
||||
virtual ~FakeSocket();
|
||||
|
||||
virtual int bind(const IP_Port *addr) = 0;
|
||||
virtual int connect(const IP_Port *addr) = 0;
|
||||
virtual int listen(int backlog) = 0;
|
||||
virtual std::unique_ptr<FakeSocket> accept(IP_Port *addr) = 0;
|
||||
|
||||
virtual int send(const uint8_t *buf, size_t len) = 0;
|
||||
virtual int recv(uint8_t *buf, size_t len) = 0;
|
||||
|
||||
virtual size_t recv_buffer_size() { return 0; }
|
||||
|
||||
virtual int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) = 0;
|
||||
virtual int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) = 0;
|
||||
|
||||
virtual int getsockopt(int level, int optname, void *optval, size_t *optlen);
|
||||
virtual int setsockopt(int level, int optname, const void *optval, size_t optlen);
|
||||
virtual int socket_nonblock(bool nonblock);
|
||||
|
||||
virtual int close();
|
||||
|
||||
bool is_nonblocking() const { return nonblocking_; }
|
||||
int type() const { return type_; }
|
||||
uint16_t local_port() const { return local_port_; }
|
||||
const IP &ip_address() const { return ip_; }
|
||||
|
||||
void set_ip(const IP &ip) { ip_ = ip; }
|
||||
|
||||
protected:
|
||||
NetworkUniverse &universe_;
|
||||
int type_;
|
||||
bool nonblocking_ = false;
|
||||
uint16_t local_port_ = 0;
|
||||
IP ip_;
|
||||
std::mutex mutex_; // Use regular mutex (recursive_mutex not fully supported in MinGW)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implements UDP logic.
|
||||
*/
|
||||
class FakeUdpSocket : public FakeSocket {
|
||||
public:
|
||||
explicit FakeUdpSocket(NetworkUniverse &universe);
|
||||
~FakeUdpSocket() override;
|
||||
|
||||
int bind(const IP_Port *addr) override;
|
||||
int connect(const IP_Port *addr) override;
|
||||
int listen(int backlog) override;
|
||||
std::unique_ptr<FakeSocket> accept(IP_Port *addr) override;
|
||||
int close() override;
|
||||
|
||||
int send(const uint8_t *buf, size_t len) override;
|
||||
int recv(uint8_t *buf, size_t len) override;
|
||||
|
||||
int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) override;
|
||||
int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) override;
|
||||
|
||||
// Called by Universe to deliver a packet
|
||||
void push_packet(std::vector<uint8_t> data, IP_Port from);
|
||||
|
||||
using PacketSource = std::function<bool(std::vector<uint8_t> &data, IP_Port &from)>;
|
||||
void set_packet_source(PacketSource source);
|
||||
|
||||
using RecvObserver = std::function<void(const std::vector<uint8_t> &data, const IP_Port &from)>;
|
||||
void set_recv_observer(RecvObserver observer);
|
||||
|
||||
private:
|
||||
void close_impl();
|
||||
|
||||
struct PendingPacket {
|
||||
std::vector<uint8_t> data;
|
||||
IP_Port from;
|
||||
};
|
||||
std::deque<PendingPacket> recv_queue_;
|
||||
PacketSource packet_source_;
|
||||
RecvObserver recv_observer_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implements TCP logic (Simplified for simulation).
|
||||
*
|
||||
* Supports:
|
||||
* - 3-way handshake simulation (instant or delayed)
|
||||
* - Stream buffering
|
||||
* - Connection states
|
||||
*/
|
||||
class FakeTcpSocket : public FakeSocket {
|
||||
public:
|
||||
enum State { CLOSED, LISTEN, SYN_SENT, SYN_RECEIVED, ESTABLISHED, CLOSE_WAIT };
|
||||
|
||||
explicit FakeTcpSocket(NetworkUniverse &universe);
|
||||
~FakeTcpSocket() override;
|
||||
|
||||
int bind(const IP_Port *addr) override;
|
||||
int connect(const IP_Port *addr) override;
|
||||
int listen(int backlog) override;
|
||||
std::unique_ptr<FakeSocket> accept(IP_Port *addr) override;
|
||||
int close() override;
|
||||
|
||||
int send(const uint8_t *buf, size_t len) override;
|
||||
int recv(uint8_t *buf, size_t len) override;
|
||||
size_t recv_buffer_size() override;
|
||||
|
||||
int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) override;
|
||||
int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) override;
|
||||
|
||||
// Internal events
|
||||
void handle_packet(const Packet &p);
|
||||
|
||||
State state() const { return state_; }
|
||||
const IP_Port &remote_addr() const { return remote_addr_; }
|
||||
|
||||
// Factory for accepting connections
|
||||
static std::unique_ptr<FakeTcpSocket> create_connected(
|
||||
NetworkUniverse &universe, const IP_Port &remote, uint16_t local_port);
|
||||
|
||||
private:
|
||||
void close_impl();
|
||||
|
||||
State state_ = CLOSED;
|
||||
IP_Port remote_addr_;
|
||||
std::deque<uint8_t> recv_buffer_;
|
||||
std::deque<std::unique_ptr<FakeTcpSocket>> pending_connections_;
|
||||
int backlog_ = 0;
|
||||
|
||||
uint32_t next_seq_ = 0;
|
||||
uint32_t last_ack_ = 0;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H
|
||||
125
testing/support/doubles/fake_sockets_test.cc
Normal file
125
testing/support/doubles/fake_sockets_test.cc
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "fake_sockets.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "network_universe.hh"
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
class FakeTcpSocketTest : public ::testing::Test {
|
||||
public:
|
||||
~FakeTcpSocketTest() override;
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
FakeTcpSocket server{universe};
|
||||
FakeTcpSocket client{universe};
|
||||
};
|
||||
|
||||
FakeTcpSocketTest::~FakeTcpSocketTest() = default;
|
||||
|
||||
TEST_F(FakeTcpSocketTest, ConnectAndAccept)
|
||||
{
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
server_addr.port = net_htons(8080);
|
||||
|
||||
ASSERT_EQ(server.bind(&server_addr), 0);
|
||||
ASSERT_EQ(server.listen(5), 0);
|
||||
|
||||
// Client connects
|
||||
ASSERT_EQ(client.connect(&server_addr), -1);
|
||||
ASSERT_EQ(errno, EINPROGRESS);
|
||||
|
||||
// Process events (Client SYN -> Server)
|
||||
universe.process_events(0);
|
||||
|
||||
// Server accepts (SYN-ACK -> Client)
|
||||
universe.process_events(0);
|
||||
|
||||
// Client receives SYN-ACK, sends ACK (ACK -> Server)
|
||||
universe.process_events(0);
|
||||
|
||||
// Server receives ACK, connection established
|
||||
IP_Port client_addr;
|
||||
auto accepted = server.accept(&client_addr);
|
||||
ASSERT_NE(accepted, nullptr);
|
||||
auto *accepted_tcp = static_cast<FakeTcpSocket *>(accepted.get());
|
||||
EXPECT_EQ(accepted_tcp->state(), FakeTcpSocket::ESTABLISHED);
|
||||
|
||||
EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED);
|
||||
}
|
||||
|
||||
TEST_F(FakeTcpSocketTest, DataTransfer)
|
||||
{
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
server_addr.port = net_htons(8081);
|
||||
|
||||
server.bind(&server_addr);
|
||||
server.listen(5);
|
||||
client.connect(&server_addr);
|
||||
|
||||
// Handshake
|
||||
universe.process_events(0); // SYN
|
||||
universe.process_events(0); // SYN-ACK
|
||||
universe.process_events(0); // ACK
|
||||
|
||||
auto accepted = server.accept(nullptr);
|
||||
ASSERT_NE(accepted, nullptr);
|
||||
|
||||
// Send data Client -> Server
|
||||
uint8_t send_buf[] = "Hello";
|
||||
ASSERT_EQ(client.send(send_buf, 5), 5);
|
||||
|
||||
universe.process_events(0); // Data packet
|
||||
|
||||
uint8_t recv_buf[10];
|
||||
ASSERT_EQ(accepted->recv(recv_buf, 10), 5);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 5), "Hello");
|
||||
}
|
||||
|
||||
class FakeUdpSocketTest : public ::testing::Test {
|
||||
public:
|
||||
~FakeUdpSocketTest() override;
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
FakeUdpSocket server{universe};
|
||||
FakeUdpSocket client{universe};
|
||||
};
|
||||
|
||||
FakeUdpSocketTest::~FakeUdpSocketTest() = default;
|
||||
|
||||
TEST_F(FakeUdpSocketTest, BindAndSendTo)
|
||||
{
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
server_addr.port = net_htons(9000);
|
||||
|
||||
ASSERT_EQ(server.bind(&server_addr), 0);
|
||||
|
||||
const char *message = "UDP Packet";
|
||||
ASSERT_EQ(client.sendto(
|
||||
reinterpret_cast<const uint8_t *>(message), strlen(message), &server_addr),
|
||||
strlen(message));
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
IP_Port sender_addr;
|
||||
uint8_t recv_buf[100];
|
||||
int len = server.recvfrom(recv_buf, sizeof(recv_buf), &sender_addr);
|
||||
|
||||
ASSERT_GT(len, 0);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), len), message);
|
||||
EXPECT_EQ(sender_addr.port, net_htons(client.local_port()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
|
||||
// end of file
|
||||
145
testing/support/doubles/network_universe.cc
Normal file
145
testing/support/doubles/network_universe.cc
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "network_universe.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "fake_sockets.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const
|
||||
{
|
||||
if (port != other.port)
|
||||
return port < other.port;
|
||||
if (ip.family.value != other.ip.family.value)
|
||||
return ip.family.value < other.ip.family.value;
|
||||
|
||||
if (net_family_is_ipv4(ip.family)) {
|
||||
return ip.ip.v4.uint32 < other.ip.ip.v4.uint32;
|
||||
}
|
||||
|
||||
return std::memcmp(&ip.ip.v6, &other.ip.ip.v6, sizeof(ip.ip.v6)) < 0;
|
||||
}
|
||||
|
||||
NetworkUniverse::NetworkUniverse() { }
|
||||
NetworkUniverse::~NetworkUniverse() { }
|
||||
|
||||
bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
IP_Port_Key key{ip, port};
|
||||
if (udp_bindings_.count(key))
|
||||
return false;
|
||||
udp_bindings_[key] = socket;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkUniverse::unbind_udp(IP ip, uint16_t port)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
udp_bindings_.erase({ip, port});
|
||||
}
|
||||
|
||||
bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
tcp_bindings_.insert({{ip, port}, socket});
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
auto range = tcp_bindings_.equal_range({ip, port});
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
if (it->second == socket) {
|
||||
tcp_bindings_.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkUniverse::send_packet(Packet p)
|
||||
{
|
||||
// Apply filters
|
||||
for (const auto &filter : filters_) {
|
||||
if (!filter(p))
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify observers
|
||||
for (const auto &observer : observers_) {
|
||||
observer(p);
|
||||
}
|
||||
|
||||
p.delivery_time += global_latency_ms_;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
event_queue_.push(std::move(p));
|
||||
}
|
||||
|
||||
void NetworkUniverse::process_events(uint64_t current_time_ms)
|
||||
{
|
||||
while (true) {
|
||||
Packet p;
|
||||
std::vector<FakeTcpSocket *> tcp_targets;
|
||||
FakeUdpSocket *udp_target = nullptr;
|
||||
bool has_packet = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) {
|
||||
p = event_queue_.top();
|
||||
event_queue_.pop();
|
||||
has_packet = true;
|
||||
|
||||
if (p.is_tcp) {
|
||||
auto range = tcp_bindings_.equal_range({p.to.ip, net_ntohs(p.to.port)});
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
tcp_targets.push_back(it->second);
|
||||
}
|
||||
} else {
|
||||
if (udp_bindings_.count({p.to.ip, net_ntohs(p.to.port)})) {
|
||||
udp_target = udp_bindings_[{p.to.ip, net_ntohs(p.to.port)}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_packet) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.is_tcp) {
|
||||
for (auto *it : tcp_targets) {
|
||||
it->handle_packet(p);
|
||||
}
|
||||
} else {
|
||||
if (udp_target) {
|
||||
udp_target->push_packet(std::move(p.data), p.from);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkUniverse::set_latency(uint64_t ms) { global_latency_ms_ = ms; }
|
||||
|
||||
void NetworkUniverse::set_verbose(bool verbose) { verbose_ = verbose; }
|
||||
|
||||
bool NetworkUniverse::is_verbose() const { return verbose_; }
|
||||
|
||||
void NetworkUniverse::add_filter(PacketFilter filter) { filters_.push_back(std::move(filter)); }
|
||||
|
||||
void NetworkUniverse::add_observer(PacketSink sink) { observers_.push_back(std::move(sink)); }
|
||||
|
||||
uint16_t NetworkUniverse::find_free_port(IP ip, uint16_t start)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
for (uint16_t port = start; port < 65535; ++port) {
|
||||
if (!udp_bindings_.count({ip, port}))
|
||||
return port;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
90
testing/support/doubles/network_universe.hh
Normal file
90
testing/support/doubles/network_universe.hh
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../toxcore/network.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeUdpSocket;
|
||||
class FakeTcpSocket;
|
||||
|
||||
struct Packet {
|
||||
IP_Port from;
|
||||
IP_Port to;
|
||||
std::vector<uint8_t> data;
|
||||
uint64_t delivery_time;
|
||||
bool is_tcp = false;
|
||||
|
||||
// TCP Simulation Fields
|
||||
uint8_t tcp_flags = 0; // 0x01=FIN, 0x02=SYN, 0x10=ACK
|
||||
uint32_t seq = 0;
|
||||
uint32_t ack = 0;
|
||||
|
||||
bool operator>(const Packet &other) const { return delivery_time > other.delivery_time; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The God Object for the network simulation.
|
||||
* Manages routing, latency, and connectivity.
|
||||
*/
|
||||
class NetworkUniverse {
|
||||
public:
|
||||
using PacketFilter = std::function<bool(Packet &)>;
|
||||
using PacketSink = std::function<void(const Packet &)>;
|
||||
|
||||
NetworkUniverse();
|
||||
~NetworkUniverse();
|
||||
|
||||
// Registration
|
||||
// Returns true if binding succeeded
|
||||
bool bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket);
|
||||
void unbind_udp(IP ip, uint16_t port);
|
||||
|
||||
bool bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket);
|
||||
void unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket);
|
||||
|
||||
// Routing
|
||||
void send_packet(Packet p);
|
||||
|
||||
// Simulation
|
||||
void process_events(uint64_t current_time_ms);
|
||||
void set_latency(uint64_t ms);
|
||||
void set_verbose(bool verbose);
|
||||
bool is_verbose() const;
|
||||
void add_filter(PacketFilter filter);
|
||||
void add_observer(PacketSink sink);
|
||||
|
||||
// Helpers
|
||||
// Finds a free port starting from 'start'
|
||||
uint16_t find_free_port(IP ip, uint16_t start = 33445);
|
||||
|
||||
struct IP_Port_Key {
|
||||
IP ip;
|
||||
uint16_t port;
|
||||
bool operator<(const IP_Port_Key &other) const;
|
||||
};
|
||||
|
||||
private:
|
||||
std::map<IP_Port_Key, FakeUdpSocket *> udp_bindings_;
|
||||
std::multimap<IP_Port_Key, FakeTcpSocket *> tcp_bindings_;
|
||||
|
||||
std::priority_queue<Packet, std::vector<Packet>, std::greater<Packet>> event_queue_;
|
||||
std::vector<PacketFilter> filters_;
|
||||
std::vector<PacketSink> observers_;
|
||||
|
||||
uint64_t global_latency_ms_ = 0;
|
||||
bool verbose_ = false;
|
||||
std::recursive_mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H
|
||||
240
testing/support/doubles/network_universe_test.cc
Normal file
240
testing/support/doubles/network_universe_test.cc
Normal file
@@ -0,0 +1,240 @@
|
||||
#include "network_universe.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "fake_sockets.hh"
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
class NetworkUniverseTest : public ::testing::Test {
|
||||
public:
|
||||
~NetworkUniverseTest() override;
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
FakeUdpSocket s1{universe};
|
||||
FakeUdpSocket s2{universe};
|
||||
};
|
||||
|
||||
NetworkUniverseTest::~NetworkUniverseTest() = default;
|
||||
|
||||
TEST_F(NetworkUniverseTest, LatencySimulation)
|
||||
{
|
||||
universe.set_latency(100);
|
||||
|
||||
IP_Port s2_addr;
|
||||
ip_init(&s2_addr.ip, false);
|
||||
s2_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
s2_addr.port = net_htons(9004);
|
||||
s2.bind(&s2_addr);
|
||||
|
||||
uint8_t data[] = "Ping";
|
||||
s1.sendto(data, 4, &s2_addr);
|
||||
|
||||
// Time 0: packet sent but delivery time is 100
|
||||
universe.process_events(0);
|
||||
IP_Port from;
|
||||
uint8_t buf[10];
|
||||
ASSERT_EQ(s2.recvfrom(buf, 10, &from), -1);
|
||||
|
||||
// Time 50: still not delivered
|
||||
universe.process_events(50);
|
||||
ASSERT_EQ(s2.recvfrom(buf, 10, &from), -1);
|
||||
|
||||
// Time 100: delivered
|
||||
universe.process_events(100);
|
||||
ASSERT_EQ(s2.recvfrom(buf, 10, &from), 4);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, RoutesBasedOnIpAndPort)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x01010101);
|
||||
ip_init(&ip2, false);
|
||||
ip2.ip.v4.uint32 = net_htonl(0x02020202);
|
||||
|
||||
uint16_t port = 33445;
|
||||
|
||||
FakeUdpSocket sock1{universe};
|
||||
FakeUdpSocket sock2{universe};
|
||||
|
||||
sock1.set_ip(ip1);
|
||||
sock2.set_ip(ip2);
|
||||
|
||||
IP_Port addr1{ip1, net_htons(port)};
|
||||
IP_Port addr2{ip2, net_htons(port)};
|
||||
|
||||
ASSERT_EQ(sock1.bind(&addr1), 0);
|
||||
ASSERT_EQ(sock2.bind(&addr2), 0);
|
||||
|
||||
const char *msg1 = "To IP 1";
|
||||
const char *msg2 = "To IP 2";
|
||||
|
||||
FakeUdpSocket sender{universe};
|
||||
sender.sendto(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1), &addr1);
|
||||
sender.sendto(reinterpret_cast<const uint8_t *>(msg2), strlen(msg2), &addr2);
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
uint8_t buf[100];
|
||||
IP_Port from;
|
||||
|
||||
int len1 = sock1.recvfrom(buf, sizeof(buf), &from);
|
||||
ASSERT_GT(len1, 0);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len1)), msg1);
|
||||
|
||||
int len2 = sock2.recvfrom(buf, sizeof(buf), &from);
|
||||
ASSERT_GT(len2, 0);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len2)), msg2);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, FindFreePortIsIpSpecific)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x01010101);
|
||||
ip_init(&ip2, false);
|
||||
ip2.ip.v4.uint32 = net_htonl(0x02020202);
|
||||
|
||||
FakeUdpSocket sock1{universe};
|
||||
sock1.set_ip(ip1);
|
||||
|
||||
uint16_t port = 33445;
|
||||
IP_Port addr1{ip1, net_htons(port)};
|
||||
ASSERT_EQ(sock1.bind(&addr1), 0);
|
||||
|
||||
// Port 33445 should be busy for ip1, but free for ip2
|
||||
EXPECT_EQ(universe.find_free_port(ip1, port), port + 1);
|
||||
EXPECT_EQ(universe.find_free_port(ip2, port), port);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, IpPortKeyEqualityRobustness)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
ip_init(&ip2, false);
|
||||
ip2.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
|
||||
uint16_t port = 12345;
|
||||
|
||||
// Force different garbage in the union padding for IPv4
|
||||
// The union is 16 bytes. IP4 is 4 bytes. Trailing 12 bytes are unused.
|
||||
memset(ip1.ip.v6.uint8 + 4, 0x11, 12);
|
||||
memset(ip2.ip.v6.uint8 + 4, 0x22, 12);
|
||||
|
||||
NetworkUniverse::IP_Port_Key key1{ip1, port};
|
||||
NetworkUniverse::IP_Port_Key key2{ip2, port};
|
||||
|
||||
// They should be considered equal (neither is less than the other)
|
||||
EXPECT_FALSE(key1 < key2);
|
||||
EXPECT_FALSE(key2 < key1);
|
||||
|
||||
// Now try with different IPv4 but same garbage
|
||||
ip2.ip.v4.uint32 = net_htonl(0x7F000002);
|
||||
memset(ip2.ip.v6.uint8 + 4, 0x11, 12); // same garbage as ip1
|
||||
NetworkUniverse::IP_Port_Key key3{ip2, port};
|
||||
|
||||
EXPECT_TRUE(key1 < key3 || key3 < key1);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, IPv4v6Distinction)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false); // IPv4
|
||||
ip1.ip.v4.uint32 = net_htonl(0x01020304);
|
||||
ip_init(&ip2, true); // IPv6
|
||||
// Set IPv6 bytes to match IPv4 bytes at the beginning
|
||||
memset(ip2.ip.v6.uint8, 0, 16);
|
||||
ip2.ip.v6.uint32[0] = net_htonl(0x01020304);
|
||||
|
||||
uint16_t port = 12345;
|
||||
|
||||
NetworkUniverse::IP_Port_Key key1{ip1, port};
|
||||
NetworkUniverse::IP_Port_Key key2{ip2, port};
|
||||
|
||||
// Different families must be different even if underlying bytes happen to match
|
||||
EXPECT_TRUE(key1 < key2 || key2 < key1);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, ManyNodes)
|
||||
{
|
||||
const int num_nodes = 5000;
|
||||
struct NodeInfo {
|
||||
std::unique_ptr<FakeUdpSocket> sock;
|
||||
IP_Port addr;
|
||||
};
|
||||
std::vector<NodeInfo> nodes;
|
||||
nodes.reserve(num_nodes);
|
||||
|
||||
for (int i = 0; i < num_nodes; ++i) {
|
||||
auto sock = std::make_unique<FakeUdpSocket>(universe);
|
||||
IP ip{};
|
||||
ip_init(&ip, false);
|
||||
ip.ip.v4.uint32 = net_htonl(0x0A000000 + i); // 10.0.x.y
|
||||
sock->set_ip(ip);
|
||||
|
||||
IP_Port addr{ip, net_htons(33445)};
|
||||
ASSERT_EQ(sock->bind(&addr), 0);
|
||||
nodes.push_back({std::move(sock), addr});
|
||||
}
|
||||
|
||||
const int num_messages = 100;
|
||||
// Send messages from first num_messages to last num_messages nodes
|
||||
for (int i = 0; i < num_messages; ++i) {
|
||||
const char *msg = "Stress test";
|
||||
nodes[i].sock->sendto(reinterpret_cast<const uint8_t *>(msg), strlen(msg),
|
||||
&nodes[num_nodes - 1 - i].addr);
|
||||
}
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
for (int i = 0; i < num_messages; ++i) {
|
||||
uint8_t buf[100];
|
||||
IP_Port from;
|
||||
int len = nodes[num_nodes - 1 - i].sock->recvfrom(buf, sizeof(buf), &from);
|
||||
ASSERT_GT(len, 0);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len)),
|
||||
"Stress test");
|
||||
EXPECT_TRUE(ip_equal(&from.ip, &nodes[i].addr.ip));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, IpPadding)
|
||||
{
|
||||
IP ip1{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
|
||||
FakeUdpSocket sock{universe};
|
||||
sock.set_ip(ip1);
|
||||
IP_Port bind_addr{ip1, net_htons(12345)};
|
||||
ASSERT_EQ(sock.bind(&bind_addr), 0);
|
||||
|
||||
// Create an address with garbage in the padding
|
||||
IP_Port target_addr;
|
||||
memset(&target_addr, 0xAA, sizeof(target_addr));
|
||||
ip_init(&target_addr.ip, false);
|
||||
target_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
target_addr.port = net_htons(12345);
|
||||
|
||||
FakeUdpSocket sender{universe};
|
||||
const char *msg = "Padding test";
|
||||
sender.sendto(reinterpret_cast<const uint8_t *>(msg), strlen(msg), &target_addr);
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
uint8_t buf[100];
|
||||
IP_Port from;
|
||||
int len = sock.recvfrom(buf, sizeof(buf), &from);
|
||||
|
||||
// If this fails, it means NetworkUniverse is not robust against padding garbage
|
||||
ASSERT_GT(len, 0);
|
||||
EXPECT_EQ(
|
||||
std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len)), "Padding test");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
28
testing/support/public/clock.hh
Normal file
28
testing/support/public/clock.hh
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_CLOCK_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_CLOCK_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
/**
|
||||
* @brief Abstraction over the system's monotonic clock.
|
||||
*/
|
||||
class ClockSystem {
|
||||
public:
|
||||
virtual ~ClockSystem();
|
||||
|
||||
/**
|
||||
* @brief Returns current monotonic time in milliseconds.
|
||||
*/
|
||||
virtual uint64_t current_time_ms() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns current monotonic time in seconds.
|
||||
*/
|
||||
virtual uint64_t current_time_s() const = 0;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_CLOCK_H
|
||||
47
testing/support/public/environment.hh
Normal file
47
testing/support/public/environment.hh
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_ENVIRONMENT_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_ENVIRONMENT_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class NetworkSystem;
|
||||
class ClockSystem;
|
||||
class RandomSystem;
|
||||
class MemorySystem;
|
||||
|
||||
/**
|
||||
* @brief Service locator for system resources in tests.
|
||||
*
|
||||
* This interface allows tests to access system resources (Network, Time, RNG,
|
||||
* memory) in a way that can be swapped between real implementations (for
|
||||
* integration tests) and simulated ones (for unit/fuzz tests).
|
||||
*/
|
||||
class Environment {
|
||||
public:
|
||||
virtual ~Environment();
|
||||
|
||||
/**
|
||||
* @brief Access the network subsystem.
|
||||
*/
|
||||
virtual NetworkSystem &network() = 0;
|
||||
|
||||
/**
|
||||
* @brief Access the monotonic clock and timer subsystem.
|
||||
*/
|
||||
virtual ClockSystem &clock() = 0;
|
||||
|
||||
/**
|
||||
* @brief Access the random number generator.
|
||||
*/
|
||||
virtual RandomSystem &random() = 0;
|
||||
|
||||
/**
|
||||
* @brief Access the memory allocator.
|
||||
*/
|
||||
virtual MemorySystem &memory() = 0;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_ENVIRONMENT_H
|
||||
183
testing/support/public/fuzz_data.hh
Normal file
183
testing/support/public/fuzz_data.hh
Normal file
@@ -0,0 +1,183 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_DATA_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_DATA_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
struct Fuzz_Data {
|
||||
static constexpr bool FUZZ_DEBUG = false;
|
||||
static constexpr std::size_t TRACE_TRAP = -1;
|
||||
|
||||
private:
|
||||
const uint8_t *data_;
|
||||
const uint8_t *base_;
|
||||
std::size_t size_;
|
||||
|
||||
public:
|
||||
Fuzz_Data(const uint8_t *input_data, std::size_t input_size)
|
||||
: data_(input_data)
|
||||
, base_(input_data)
|
||||
, size_(input_size)
|
||||
{
|
||||
}
|
||||
|
||||
Fuzz_Data &operator=(const Fuzz_Data &rhs) = delete;
|
||||
Fuzz_Data(const Fuzz_Data &rhs) = delete;
|
||||
|
||||
struct Consumer {
|
||||
const char *func;
|
||||
Fuzz_Data &fd;
|
||||
|
||||
operator bool()
|
||||
{
|
||||
if (fd.empty())
|
||||
return false;
|
||||
const bool val = fd.data_[0];
|
||||
if (FUZZ_DEBUG) {
|
||||
std::printf("consume@%zu(%s): bool %s\n", fd.pos(), func, val ? "true" : "false");
|
||||
}
|
||||
++fd.data_;
|
||||
--fd.size_;
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
operator T()
|
||||
{
|
||||
if (sizeof(T) > fd.size())
|
||||
return T{};
|
||||
const uint8_t *bytes = fd.consume(func, sizeof(T));
|
||||
T val;
|
||||
std::memcpy(&val, bytes, sizeof(T));
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
Consumer consume1(const char *func) { return Consumer{func, *this}; }
|
||||
|
||||
template <typename T>
|
||||
T consume_integral()
|
||||
{
|
||||
return consume1("consume_integral");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T consume_integral_in_range(T min, T max)
|
||||
{
|
||||
if (min >= max)
|
||||
return min;
|
||||
T val = consume_integral<T>();
|
||||
return min + (val % (max - min + 1));
|
||||
}
|
||||
|
||||
std::size_t remaining_bytes() const { return size(); }
|
||||
|
||||
std::vector<uint8_t> consume_bytes(std::size_t count)
|
||||
{
|
||||
if (count == 0 || count > size_)
|
||||
return {};
|
||||
const uint8_t *start = consume("consume_bytes", count);
|
||||
if (!start)
|
||||
return {};
|
||||
return std::vector<uint8_t>(start, start + count);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> consume_remaining_bytes()
|
||||
{
|
||||
if (empty())
|
||||
return {};
|
||||
std::size_t count = size();
|
||||
const uint8_t *start = consume("consume_remaining_bytes", count);
|
||||
return std::vector<uint8_t>(start, start + count);
|
||||
}
|
||||
|
||||
std::size_t size() const { return size_; }
|
||||
std::size_t pos() const { return data_ - base_; }
|
||||
const uint8_t *data() const { return data_; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
|
||||
const uint8_t *consume(const char *func, std::size_t count)
|
||||
{
|
||||
if (count > size_)
|
||||
return nullptr;
|
||||
const uint8_t *val = data_;
|
||||
if (FUZZ_DEBUG) {
|
||||
if (count == 1) {
|
||||
std::printf("consume@%zu(%s): %d (0x%02x)\n", pos(), func, val[0], val[0]);
|
||||
} else if (count != 0) {
|
||||
std::printf("consume@%zu(%s): %02x..%02x[%zu]\n", pos(), func, val[0],
|
||||
val[count - 1], count);
|
||||
}
|
||||
}
|
||||
data_ += count;
|
||||
size_ -= count;
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
#define CONSUME1_OR_RETURN(TYPE, NAME, INPUT) \
|
||||
if ((INPUT).size() < sizeof(TYPE)) { \
|
||||
return; \
|
||||
} \
|
||||
TYPE NAME = (INPUT).consume1(__func__)
|
||||
|
||||
#define CONSUME1_OR_RETURN_VAL(TYPE, NAME, INPUT, VAL) \
|
||||
if ((INPUT).size() < sizeof(TYPE)) { \
|
||||
return (VAL); \
|
||||
} \
|
||||
TYPE NAME = (INPUT).consume1(__func__)
|
||||
|
||||
#define CONSUME_OR_RETURN(DECL, INPUT, SIZE) \
|
||||
if ((INPUT).size() < (SIZE)) { \
|
||||
return; \
|
||||
} \
|
||||
DECL = (INPUT).consume(__func__, (SIZE))
|
||||
|
||||
#define CONSUME_OR_RETURN_VAL(DECL, INPUT, SIZE, VAL) \
|
||||
if ((INPUT).size() < (SIZE)) { \
|
||||
return (VAL); \
|
||||
} \
|
||||
DECL = (INPUT).consume(__func__, (SIZE))
|
||||
|
||||
using Fuzz_Target = void (*)(Fuzz_Data &input);
|
||||
|
||||
template <Fuzz_Target... Args>
|
||||
struct Fuzz_Target_Selector;
|
||||
|
||||
template <Fuzz_Target Arg, Fuzz_Target... Args>
|
||||
struct Fuzz_Target_Selector<Arg, Args...> {
|
||||
static void select(uint8_t selector, Fuzz_Data &input)
|
||||
{
|
||||
if (selector == sizeof...(Args)) {
|
||||
return Arg(input);
|
||||
}
|
||||
return Fuzz_Target_Selector<Args...>::select(selector, input);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Fuzz_Target_Selector<> {
|
||||
static void select(uint8_t selector, Fuzz_Data &input)
|
||||
{
|
||||
// The selector selected no function, so we do nothing and rely on the
|
||||
// fuzzer to come up with a better selector.
|
||||
}
|
||||
};
|
||||
|
||||
template <Fuzz_Target... Args>
|
||||
void fuzz_select_target(const uint8_t *data, std::size_t size)
|
||||
{
|
||||
Fuzz_Data input{data, size};
|
||||
|
||||
CONSUME1_OR_RETURN(const uint8_t, selector, input);
|
||||
return Fuzz_Target_Selector<Args...>::select(selector, input);
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_DATA_H
|
||||
23
testing/support/public/fuzz_helpers.hh
Normal file
23
testing/support/public/fuzz_helpers.hh
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_HELPERS_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_HELPERS_H
|
||||
|
||||
#include "../doubles/fake_memory.hh"
|
||||
#include "../doubles/fake_random.hh"
|
||||
#include "../doubles/fake_sockets.hh"
|
||||
#include "fuzz_data.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
// Configures the socket to pull packets from the Fuzz_Data stream
|
||||
// mimicking the legacy Fuzz_System behavior (2-byte length prefix).
|
||||
void configure_fuzz_packet_source(FakeUdpSocket &socket, Fuzz_Data &input);
|
||||
|
||||
// Configures memory allocator to consume failure decisions from Fuzz_Data.
|
||||
void configure_fuzz_memory_source(FakeMemory &memory, Fuzz_Data &input);
|
||||
|
||||
// Configures random generator to consume bytes from Fuzz_Data.
|
||||
void configure_fuzz_random_source(FakeRandom &random, Fuzz_Data &input);
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_HELPERS_H
|
||||
23
testing/support/public/memory.hh
Normal file
23
testing/support/public/memory.hh
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_PUBLIC_MEMORY_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_PUBLIC_MEMORY_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
/**
|
||||
* @brief Abstraction over the memory allocator.
|
||||
*/
|
||||
class MemorySystem {
|
||||
public:
|
||||
virtual ~MemorySystem();
|
||||
|
||||
virtual void *malloc(size_t size) = 0;
|
||||
virtual void *realloc(void *ptr, size_t size) = 0;
|
||||
virtual void free(void *ptr) = 0;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_PUBLIC_MEMORY_H
|
||||
51
testing/support/public/network.hh
Normal file
51
testing/support/public/network.hh
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_NETWORK_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_NETWORK_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../toxcore/network.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
/**
|
||||
* @brief Abstraction over the network subsystem (sockets).
|
||||
*/
|
||||
class NetworkSystem {
|
||||
public:
|
||||
virtual ~NetworkSystem();
|
||||
|
||||
virtual Socket socket(int domain, int type, int protocol) = 0;
|
||||
virtual int bind(Socket sock, const IP_Port *addr) = 0;
|
||||
virtual int close(Socket sock) = 0;
|
||||
virtual int sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) = 0;
|
||||
virtual int recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr) = 0;
|
||||
|
||||
// TCP Support
|
||||
virtual int listen(Socket sock, int backlog) = 0;
|
||||
virtual Socket accept(Socket sock) = 0;
|
||||
virtual int connect(Socket sock, const IP_Port *addr) = 0;
|
||||
virtual int send(Socket sock, const uint8_t *buf, size_t len) = 0;
|
||||
virtual int recv(Socket sock, uint8_t *buf, size_t len) = 0;
|
||||
virtual int recvbuf(Socket sock) = 0;
|
||||
|
||||
// Auxiliary
|
||||
virtual int socket_nonblock(Socket sock, bool nonblock) = 0;
|
||||
virtual int getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen) = 0;
|
||||
virtual int setsockopt(Socket sock, int level, int optname, const void *optval, size_t optlen)
|
||||
= 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper to create an IPv4 IP struct from a host-byte-order address.
|
||||
*/
|
||||
IP make_ip(uint32_t ipv4);
|
||||
|
||||
/**
|
||||
* @brief Helper to create a unique node IP in the 10.x.y.z range.
|
||||
*/
|
||||
IP make_node_ip(uint32_t node_id);
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_NETWORK_H
|
||||
22
testing/support/public/random.hh
Normal file
22
testing/support/public/random.hh
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_RANDOM_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_RANDOM_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
/**
|
||||
* @brief Abstraction over the random number generator.
|
||||
*/
|
||||
class RandomSystem {
|
||||
public:
|
||||
virtual ~RandomSystem();
|
||||
|
||||
virtual uint32_t uniform(uint32_t upper_bound) = 0;
|
||||
virtual void bytes(uint8_t *out, size_t count) = 0;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_RANDOM_H
|
||||
78
testing/support/public/simulated_environment.hh
Normal file
78
testing/support/public/simulated_environment.hh
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_SIMULATED_ENVIRONMENT_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_SIMULATED_ENVIRONMENT_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include "../../../toxcore/tox_memory_impl.h"
|
||||
#include "../../../toxcore/tox_private.h"
|
||||
#include "../../../toxcore/tox_random_impl.h"
|
||||
#include "../doubles/fake_clock.hh"
|
||||
#include "../doubles/fake_memory.hh"
|
||||
#include "../doubles/fake_random.hh"
|
||||
#include "environment.hh"
|
||||
#include "simulation.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
struct ScopedToxSystem {
|
||||
// The underlying node in the simulation
|
||||
std::unique_ptr<SimulatedNode> node;
|
||||
|
||||
// Direct access to primary socket (for fuzzer injection)
|
||||
FakeUdpSocket *endpoint;
|
||||
|
||||
// C structs
|
||||
struct Network c_network;
|
||||
struct Tox_Random c_random;
|
||||
struct Tox_Memory c_memory;
|
||||
|
||||
// The main struct passed to tox_new
|
||||
Tox_System system;
|
||||
};
|
||||
|
||||
class SimulatedEnvironment : public Environment {
|
||||
public:
|
||||
SimulatedEnvironment();
|
||||
~SimulatedEnvironment() override;
|
||||
|
||||
NetworkSystem &network() override;
|
||||
ClockSystem &clock() override;
|
||||
RandomSystem &random() override;
|
||||
MemorySystem &memory() override;
|
||||
|
||||
FakeClock &fake_clock();
|
||||
FakeRandom &fake_random();
|
||||
FakeMemory &fake_memory();
|
||||
|
||||
Simulation &simulation() { return *sim_; }
|
||||
|
||||
/**
|
||||
* @brief Creates a new virtual node in the simulation bound to the specified port.
|
||||
*/
|
||||
std::unique_ptr<ScopedToxSystem> create_node(uint16_t port);
|
||||
|
||||
void advance_time(uint64_t ms);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Simulation> sim_;
|
||||
|
||||
// Global instances for Environment interface compliance.
|
||||
std::unique_ptr<FakeRandom> global_random_;
|
||||
std::unique_ptr<FakeMemory> global_memory_;
|
||||
|
||||
// For network(), we can't return a per-node stack.
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_SIMULATED_ENVIRONMENT_H
|
||||
112
testing/support/public/simulation.hh
Normal file
112
testing/support/public/simulation.hh
Normal file
@@ -0,0 +1,112 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_SIMULATION_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_SIMULATION_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include "../../../toxcore/tox.h"
|
||||
#include "../../../toxcore/tox_memory_impl.h"
|
||||
#include "../../../toxcore/tox_private.h"
|
||||
#include "../../../toxcore/tox_random_impl.h"
|
||||
#include "../doubles/fake_clock.hh"
|
||||
#include "../doubles/fake_memory.hh"
|
||||
#include "../doubles/fake_network_stack.hh"
|
||||
#include "../doubles/fake_random.hh"
|
||||
#include "../doubles/network_universe.hh"
|
||||
#include "environment.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class SimulatedNode;
|
||||
|
||||
/**
|
||||
* @brief The Simulation World.
|
||||
* Holds the Clock and the Universe.
|
||||
*/
|
||||
class Simulation {
|
||||
public:
|
||||
Simulation();
|
||||
~Simulation();
|
||||
|
||||
// Time Control
|
||||
void advance_time(uint64_t ms);
|
||||
void run_until(std::function<bool()> condition, uint64_t timeout_ms = 5000);
|
||||
|
||||
// Global Access
|
||||
FakeClock &clock() { return *clock_; }
|
||||
NetworkUniverse &net() { return *net_; }
|
||||
|
||||
// Node Factory
|
||||
std::unique_ptr<SimulatedNode> create_node();
|
||||
|
||||
private:
|
||||
std::unique_ptr<FakeClock> clock_;
|
||||
std::unique_ptr<NetworkUniverse> net_;
|
||||
uint32_t node_count_ = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents a single node in the simulation.
|
||||
* Implements the Environment interface for dependency injection.
|
||||
*/
|
||||
class SimulatedNode : public Environment {
|
||||
public:
|
||||
explicit SimulatedNode(Simulation &sim, uint32_t node_id);
|
||||
~SimulatedNode() override;
|
||||
|
||||
// Environment Interface
|
||||
NetworkSystem &network() override;
|
||||
ClockSystem &clock() override;
|
||||
RandomSystem &random() override;
|
||||
MemorySystem &memory() override;
|
||||
|
||||
// Direct Access to Fakes
|
||||
FakeNetworkStack &fake_network() { return *network_; }
|
||||
FakeRandom &fake_random() { return *random_; }
|
||||
FakeMemory &fake_memory() { return *memory_; }
|
||||
|
||||
// Tox Creation Helper
|
||||
// Returns a configured Tox instance bound to this node's environment.
|
||||
// The user owns the Tox instance.
|
||||
struct ToxDeleter {
|
||||
void operator()(Tox *t) const { tox_kill(t); }
|
||||
};
|
||||
using ToxPtr = std::unique_ptr<Tox, ToxDeleter>;
|
||||
|
||||
ToxPtr create_tox(const Tox_Options *options = nullptr);
|
||||
|
||||
// Helper to get C structs for manual injection
|
||||
struct Network get_c_network() { return network_->get_c_network(); }
|
||||
struct Tox_Random get_c_random() { return random_->get_c_random(); }
|
||||
struct Tox_Memory get_c_memory() { return memory_->get_c_memory(); }
|
||||
|
||||
// For fuzzing compatibility (exposes first bound UDP socket as "endpoint")
|
||||
FakeUdpSocket *get_primary_socket();
|
||||
|
||||
private:
|
||||
Simulation &sim_;
|
||||
std::unique_ptr<FakeNetworkStack> network_;
|
||||
std::unique_ptr<FakeRandom> random_;
|
||||
std::unique_ptr<FakeMemory> memory_;
|
||||
|
||||
// C-compatible views (must stay valid for the lifetime of Tox)
|
||||
public:
|
||||
struct Network c_network;
|
||||
struct Tox_Random c_random;
|
||||
struct Tox_Memory c_memory;
|
||||
struct IP ip;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_SIMULATION_H
|
||||
82
testing/support/public/tox_network.hh
Normal file
82
testing/support/public/tox_network.hh
Normal file
@@ -0,0 +1,82 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright © 2026 The TokTok team.
|
||||
*/
|
||||
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "simulation.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
struct ConnectedFriend {
|
||||
std::unique_ptr<SimulatedNode> node;
|
||||
SimulatedNode::ToxPtr tox;
|
||||
uint32_t friend_number;
|
||||
|
||||
ConnectedFriend(std::unique_ptr<SimulatedNode> node_in, SimulatedNode::ToxPtr tox_in,
|
||||
uint32_t friend_number_in)
|
||||
: node(std::move(node_in))
|
||||
, tox(std::move(tox_in))
|
||||
, friend_number(friend_number_in)
|
||||
{
|
||||
}
|
||||
|
||||
ConnectedFriend(ConnectedFriend &&) = default;
|
||||
ConnectedFriend &operator=(ConnectedFriend &&) = default;
|
||||
~ConnectedFriend();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sets up a network of connected Tox instances.
|
||||
*
|
||||
* This function creates num_friends Tox instances, adds them as friends to main_tox,
|
||||
* and bootstraps them to main_node. It then runs the simulation until all friends
|
||||
* are connected to main_tox.
|
||||
*
|
||||
* @param sim The simulation to run.
|
||||
* @param main_tox The main Tox instance to which friends will connect.
|
||||
* @param main_node The simulated node hosting the main Tox instance.
|
||||
* @param num_friends The number of friends to create and connect.
|
||||
* @param options Optional Tox_Options to use for the friend Tox instances.
|
||||
* @return A vector of ConnectedFriend structures, each representing a friend.
|
||||
*/
|
||||
std::vector<ConnectedFriend> setup_connected_friends(Simulation &sim, Tox *main_tox,
|
||||
SimulatedNode &main_node, int num_friends, const Tox_Options *options = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Connects two existing Tox instances as friends.
|
||||
*
|
||||
* This function adds each Tox instance as a friend to the other, bootstraps
|
||||
* them to each other, and runs the simulation until they are connected.
|
||||
*
|
||||
* @param sim The simulation to run.
|
||||
* @param node1 The simulated node hosting the first Tox instance.
|
||||
* @param tox1 The first Tox instance.
|
||||
* @param node2 The simulated node hosting the second Tox instance.
|
||||
* @param tox2 The second Tox instance.
|
||||
* @return True if connected successfully, false otherwise.
|
||||
*/
|
||||
bool connect_friends(
|
||||
Simulation &sim, SimulatedNode &node1, Tox *tox1, SimulatedNode &node2, Tox *tox2);
|
||||
|
||||
/**
|
||||
* @brief Sets up a group and has all friends join it.
|
||||
*
|
||||
* This function creates a new public group on main_tox, invites all friends in the
|
||||
* provided vector, and runs the simulation until all friends have joined and
|
||||
* main_tox sees all of them.
|
||||
*
|
||||
* @param sim The simulation to run.
|
||||
* @param main_tox The main Tox instance that creates the group.
|
||||
* @param friends The friends to invite to the group.
|
||||
* @return The group number on the main Tox instance, or UINT32_MAX on failure.
|
||||
*/
|
||||
uint32_t setup_connected_group(
|
||||
Simulation &sim, Tox *main_tox, const std::vector<ConnectedFriend> &friends);
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H
|
||||
7
testing/support/src/clock.cc
Normal file
7
testing/support/src/clock.cc
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "../public/clock.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
ClockSystem::~ClockSystem() = default;
|
||||
|
||||
} // namespace tox::test
|
||||
7
testing/support/src/environment.cc
Normal file
7
testing/support/src/environment.cc
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "../public/environment.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
Environment::~Environment() = default;
|
||||
|
||||
} // namespace tox::test
|
||||
16
testing/support/src/fake_clock.cc
Normal file
16
testing/support/src/fake_clock.cc
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "../doubles/fake_clock.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
FakeClock::FakeClock(uint64_t start_time_ms)
|
||||
: now_ms_(start_time_ms)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t FakeClock::current_time_ms() const { return now_ms_; }
|
||||
|
||||
uint64_t FakeClock::current_time_s() const { return now_ms_ / 1000; }
|
||||
|
||||
void FakeClock::advance(uint64_t ms) { now_ms_ += ms; }
|
||||
|
||||
} // namespace tox::test
|
||||
144
testing/support/src/fake_memory.cc
Normal file
144
testing/support/src/fake_memory.cc
Normal file
@@ -0,0 +1,144 @@
|
||||
#include "../doubles/fake_memory.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <new>
|
||||
|
||||
#include "../../../toxcore/tox_memory_impl.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
// --- Trampolines ---
|
||||
|
||||
static const Tox_Memory_Funcs kFakeMemoryVtable = {
|
||||
.malloc_callback
|
||||
= [](void *obj, uint32_t size) { return static_cast<FakeMemory *>(obj)->malloc(size); },
|
||||
.realloc_callback
|
||||
= [](void *obj, void *ptr,
|
||||
uint32_t size) { return static_cast<FakeMemory *>(obj)->realloc(ptr, size); },
|
||||
.dealloc_callback = [](void *obj, void *ptr) { static_cast<FakeMemory *>(obj)->free(ptr); },
|
||||
};
|
||||
|
||||
// --- Implementation ---
|
||||
|
||||
FakeMemory::FakeMemory() = default;
|
||||
FakeMemory::~FakeMemory() = default;
|
||||
|
||||
void *FakeMemory::malloc(size_t size)
|
||||
{
|
||||
bool fail = failure_injector_ && failure_injector_(size);
|
||||
|
||||
if (observer_) {
|
||||
observer_(!fail);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *ptr = std::malloc(size + sizeof(Header));
|
||||
if (!ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Header *header = static_cast<Header *>(ptr);
|
||||
header->size = size;
|
||||
header->magic = kMagic;
|
||||
|
||||
current_allocation_ += size;
|
||||
if (current_allocation_ > max_allocation_) {
|
||||
max_allocation_ = current_allocation_;
|
||||
}
|
||||
|
||||
void *res = header + 1;
|
||||
// std::cerr << "[FakeMemory] malloc(" << size << ") -> " << res << " (header=" << header << ")"
|
||||
// << std::endl;
|
||||
return res;
|
||||
}
|
||||
|
||||
void *FakeMemory::realloc(void *ptr, size_t size)
|
||||
{
|
||||
if (!ptr) {
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
Header *old_header = static_cast<Header *>(ptr) - 1;
|
||||
if (old_header->magic != kMagic) {
|
||||
if (old_header->magic == kFreeMagic) {
|
||||
std::cerr << "[FakeMemory] realloc: Double realloc/free detected at " << ptr
|
||||
<< " (header=" << old_header << ")" << std::endl;
|
||||
} else {
|
||||
std::cerr << "[FakeMemory] realloc: Invalid pointer (wrong magic 0x" << std::hex
|
||||
<< old_header->magic << ") at " << ptr << " (header=" << old_header << ")"
|
||||
<< std::endl;
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
bool fail = failure_injector_ && failure_injector_(size);
|
||||
|
||||
if (observer_) {
|
||||
observer_(!fail);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
// If realloc fails, original block is left untouched.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t old_size = old_header->size;
|
||||
void *new_ptr = std::realloc(old_header, size + sizeof(Header));
|
||||
if (!new_ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Header *header = static_cast<Header *>(new_ptr);
|
||||
current_allocation_ -= old_size;
|
||||
current_allocation_ += size;
|
||||
if (current_allocation_ > max_allocation_) {
|
||||
max_allocation_ = current_allocation_;
|
||||
}
|
||||
|
||||
header->size = size;
|
||||
header->magic = kMagic;
|
||||
void *res = header + 1;
|
||||
// std::cerr << "[FakeMemory] realloc(" << ptr << ", " << size << ") -> " << res << " (header="
|
||||
// << header << ")" << std::endl;
|
||||
return res;
|
||||
}
|
||||
|
||||
void FakeMemory::free(void *ptr)
|
||||
{
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Header *header = static_cast<Header *>(ptr) - 1;
|
||||
if (header->magic != kMagic) {
|
||||
if (header->magic == kFreeMagic) {
|
||||
std::cerr << "[FakeMemory] free: Double free detected at " << ptr
|
||||
<< " (header=" << header << ")" << std::endl;
|
||||
} else {
|
||||
std::cerr << "[FakeMemory] free: Invalid pointer (wrong magic 0x" << std::hex
|
||||
<< header->magic << ") at " << ptr << " (header=" << header << ")"
|
||||
<< std::endl;
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
size_t size = header->size;
|
||||
current_allocation_ -= size;
|
||||
header->magic = kFreeMagic; // Mark as free
|
||||
std::free(header);
|
||||
}
|
||||
|
||||
void FakeMemory::set_failure_injector(FailureInjector injector)
|
||||
{
|
||||
failure_injector_ = std::move(injector);
|
||||
}
|
||||
|
||||
void FakeMemory::set_observer(Observer observer) { observer_ = std::move(observer); }
|
||||
|
||||
struct Tox_Memory FakeMemory::get_c_memory() { return Tox_Memory{&kFakeMemoryVtable, this}; }
|
||||
|
||||
} // namespace tox::test
|
||||
63
testing/support/src/fake_random.cc
Normal file
63
testing/support/src/fake_random.cc
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "../doubles/fake_random.hh"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "../../../toxcore/tox_random_impl.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
// --- Trampolines for Tox_Random_Funcs ---
|
||||
|
||||
static const Tox_Random_Funcs kFakeRandomVtable = {
|
||||
.bytes_callback
|
||||
= [](void *obj, uint8_t *bytes,
|
||||
uint32_t length) { static_cast<FakeRandom *>(obj)->bytes(bytes, length); },
|
||||
.uniform_callback
|
||||
= [](void *obj,
|
||||
uint32_t upper_bound) { return static_cast<FakeRandom *>(obj)->uniform(upper_bound); },
|
||||
};
|
||||
|
||||
FakeRandom::FakeRandom(uint64_t seed)
|
||||
: rng_(seed)
|
||||
{
|
||||
}
|
||||
|
||||
void FakeRandom::set_entropy_source(EntropySource source) { entropy_source_ = std::move(source); }
|
||||
|
||||
void FakeRandom::set_observer(Observer observer) { observer_ = std::move(observer); }
|
||||
|
||||
uint32_t FakeRandom::uniform(uint32_t upper_bound)
|
||||
{
|
||||
if (upper_bound == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we are recording (observer set) or replaying (entropy source set),
|
||||
// we want consistency. Legacy behavior derived uniform from bytes.
|
||||
if (entropy_source_ || observer_) {
|
||||
uint32_t raw_val = 0;
|
||||
bytes(reinterpret_cast<uint8_t *>(&raw_val), sizeof(raw_val));
|
||||
return raw_val % upper_bound;
|
||||
}
|
||||
|
||||
std::uniform_int_distribution<uint32_t> dist(0, upper_bound - 1);
|
||||
return dist(rng_);
|
||||
}
|
||||
|
||||
void FakeRandom::bytes(uint8_t *out, size_t count)
|
||||
{
|
||||
if (entropy_source_) {
|
||||
entropy_source_(out, count);
|
||||
} else {
|
||||
std::uniform_int_distribution<uint16_t> dist(0, 255);
|
||||
std::generate_n(out, count, [&]() { return static_cast<uint8_t>(dist(rng_)); });
|
||||
}
|
||||
|
||||
if (observer_) {
|
||||
observer_(out, count);
|
||||
}
|
||||
}
|
||||
|
||||
struct Tox_Random FakeRandom::get_c_random() { return Tox_Random{&kFakeRandomVtable, this}; }
|
||||
|
||||
} // namespace tox::test
|
||||
96
testing/support/src/fuzz_helpers.cc
Normal file
96
testing/support/src/fuzz_helpers.cc
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "../public/fuzz_helpers.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
void configure_fuzz_packet_source(FakeUdpSocket &socket, Fuzz_Data &input)
|
||||
{
|
||||
socket.set_packet_source([&input](std::vector<uint8_t> &data, IP_Port &from) -> bool {
|
||||
if (input.size() < 2)
|
||||
return false;
|
||||
|
||||
const uint8_t *len_bytes = input.consume("recv_len", 2);
|
||||
if (!len_bytes)
|
||||
return false;
|
||||
|
||||
uint16_t len = (len_bytes[0] << 8) | len_bytes[1];
|
||||
|
||||
if (len == 0xffff) {
|
||||
// EWOULDBLOCK simulation. Return false implies "no packet".
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t actual_len = std::min(static_cast<size_t>(len), input.size());
|
||||
const uint8_t *payload = input.consume("recv_payload", actual_len);
|
||||
|
||||
if (!payload && actual_len > 0)
|
||||
return false;
|
||||
|
||||
data.assign(payload, payload + actual_len);
|
||||
|
||||
// Dummy address (legacy Fuzz_System used 127.0.0.2:33446)
|
||||
ip_init(&from.ip, false); // IPv4
|
||||
from.ip.ip.v4.uint32 = net_htonl(0x7F000002); // 127.0.0.2
|
||||
from.port = net_htons(33446);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void configure_fuzz_memory_source(FakeMemory &memory, Fuzz_Data &input)
|
||||
{
|
||||
memory.set_failure_injector([&input](size_t size) -> bool {
|
||||
if (input.size() < 1) {
|
||||
// Legacy behavior: if input runs out, allocation succeeds.
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *b = input.consume("malloc_decision", 1);
|
||||
bool succeed = (b && *b);
|
||||
return !succeed; // Return true to fail
|
||||
});
|
||||
}
|
||||
|
||||
void configure_fuzz_random_source(FakeRandom &random, Fuzz_Data &input)
|
||||
{
|
||||
random.set_entropy_source([&input](uint8_t *out, size_t count) {
|
||||
// Initialize with zeros in case of underflow
|
||||
std::memset(out, 0, count);
|
||||
|
||||
// For small types (keys, nonces), behave like legacy fuzz support
|
||||
if (count == 1 || count == 2 || count == 4 || count == 8) {
|
||||
if (input.size() >= count) {
|
||||
const uint8_t *bytes = input.consume("rnd_bytes", count);
|
||||
std::memcpy(out, bytes, count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For large chunks, repeat available data
|
||||
if (count == 24 || count == 32) {
|
||||
size_t available = std::min(input.size(), static_cast<size_t>(2));
|
||||
if (available > 0) {
|
||||
const uint8_t *chunk = input.consume("rnd_chunk", available);
|
||||
if (available == 2) {
|
||||
std::memset(out, chunk[0], count / 2);
|
||||
std::memset(out + count / 2, chunk[1], count / 2);
|
||||
} else {
|
||||
std::memset(out, chunk[0], count);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback for other sizes: consume as much as possible
|
||||
size_t taken = std::min(input.size(), count);
|
||||
if (taken > 0) {
|
||||
const uint8_t *bytes = input.consume("rnd_generic", taken);
|
||||
std::memcpy(out, bytes, taken);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
7
testing/support/src/memory.cc
Normal file
7
testing/support/src/memory.cc
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "../public/memory.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
MemorySystem::~MemorySystem() = default;
|
||||
|
||||
} // namespace tox::test
|
||||
21
testing/support/src/network.cc
Normal file
21
testing/support/src/network.cc
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "../public/network.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
NetworkSystem::~NetworkSystem() = default;
|
||||
|
||||
IP make_ip(uint32_t ipv4)
|
||||
{
|
||||
IP ip;
|
||||
ip_init(&ip, false);
|
||||
ip.ip.v4.uint32 = net_htonl(ipv4);
|
||||
return ip;
|
||||
}
|
||||
|
||||
IP make_node_ip(uint32_t node_id)
|
||||
{
|
||||
// Use 10.x.y.z range: 10. (id >> 16) . (id >> 8) . (id & 0xFF)
|
||||
return make_ip(0x0A000000 | (node_id & 0x00FFFFFF));
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
7
testing/support/src/random.cc
Normal file
7
testing/support/src/random.cc
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "../public/random.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
RandomSystem::~RandomSystem() = default;
|
||||
|
||||
} // namespace tox::test
|
||||
76
testing/support/src/simulated_environment.cc
Normal file
76
testing/support/src/simulated_environment.cc
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "../public/simulated_environment.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
SimulatedEnvironment::SimulatedEnvironment()
|
||||
: sim_(std::make_unique<Simulation>())
|
||||
, global_random_(std::make_unique<FakeRandom>(12345))
|
||||
, global_memory_(std::make_unique<FakeMemory>())
|
||||
{
|
||||
}
|
||||
|
||||
SimulatedEnvironment::~SimulatedEnvironment() = default;
|
||||
|
||||
NetworkSystem &SimulatedEnvironment::network()
|
||||
{
|
||||
// Return a dummy stack for interface compliance; real networking is per-node.
|
||||
static FakeNetworkStack *dummy = nullptr;
|
||||
if (!dummy) {
|
||||
IP dummy_ip;
|
||||
ip_init(&dummy_ip, false);
|
||||
dummy = new FakeNetworkStack(sim_->net(), dummy_ip);
|
||||
}
|
||||
return *dummy;
|
||||
}
|
||||
|
||||
ClockSystem &SimulatedEnvironment::clock() { return sim_->clock(); }
|
||||
RandomSystem &SimulatedEnvironment::random() { return *global_random_; }
|
||||
MemorySystem &SimulatedEnvironment::memory() { return *global_memory_; }
|
||||
|
||||
FakeClock &SimulatedEnvironment::fake_clock() { return sim_->clock(); }
|
||||
FakeRandom &SimulatedEnvironment::fake_random() { return *global_random_; }
|
||||
FakeMemory &SimulatedEnvironment::fake_memory() { return *global_memory_; }
|
||||
|
||||
std::unique_ptr<ScopedToxSystem> SimulatedEnvironment::create_node(uint16_t port)
|
||||
{
|
||||
auto scoped = std::make_unique<ScopedToxSystem>();
|
||||
scoped->node = sim_->create_node();
|
||||
|
||||
// Bind port
|
||||
if (port != 0) {
|
||||
Socket s = scoped->node->fake_network().socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
IP_Port addr;
|
||||
ip_init(&addr.ip, false);
|
||||
addr.ip.ip.v4.uint32 = 0;
|
||||
addr.port = net_htons(port);
|
||||
scoped->node->fake_network().bind(s, &addr);
|
||||
}
|
||||
|
||||
// Get Primary Endpoint for Fuzzer
|
||||
scoped->endpoint = scoped->node->get_primary_socket();
|
||||
|
||||
// Use global Random and Memory for legacy compatibility.
|
||||
scoped->c_random = global_random_->get_c_random();
|
||||
scoped->c_memory = global_memory_->get_c_memory();
|
||||
|
||||
// Use Node's Network
|
||||
scoped->c_network = scoped->node->get_c_network();
|
||||
|
||||
// Setup System
|
||||
scoped->system.mem = &scoped->c_memory;
|
||||
scoped->system.ns = &scoped->c_network;
|
||||
scoped->system.rng = &scoped->c_random;
|
||||
|
||||
scoped->system.mono_time_user_data = &sim_->clock();
|
||||
scoped->system.mono_time_callback = [](void *user_data) -> uint64_t {
|
||||
return static_cast<FakeClock *>(user_data)->current_time_ms();
|
||||
};
|
||||
|
||||
return scoped;
|
||||
}
|
||||
|
||||
void SimulatedEnvironment::advance_time(uint64_t ms) { sim_->advance_time(ms); }
|
||||
|
||||
} // namespace tox::test
|
||||
113
testing/support/src/simulation.cc
Normal file
113
testing/support/src/simulation.cc
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "../public/simulation.hh"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
// --- Simulation ---
|
||||
|
||||
Simulation::Simulation()
|
||||
: clock_(std::make_unique<FakeClock>())
|
||||
, net_(std::make_unique<NetworkUniverse>())
|
||||
{
|
||||
}
|
||||
|
||||
Simulation::~Simulation() = default;
|
||||
|
||||
void Simulation::advance_time(uint64_t ms)
|
||||
{
|
||||
clock_->advance(ms);
|
||||
net_->process_events(clock_->current_time_ms());
|
||||
}
|
||||
|
||||
void Simulation::run_until(std::function<bool()> condition, uint64_t timeout_ms)
|
||||
{
|
||||
uint64_t start_time = clock_->current_time_ms();
|
||||
while (!condition()) {
|
||||
if (clock_->current_time_ms() - start_time > timeout_ms) {
|
||||
break;
|
||||
}
|
||||
advance_time(10); // 10ms ticks
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<SimulatedNode> Simulation::create_node()
|
||||
{
|
||||
auto node = std::make_unique<SimulatedNode>(*this, ++node_count_);
|
||||
if (net_->is_verbose()) {
|
||||
uint32_t ip4 = net_ntohl(node->ip.ip.v4.uint32);
|
||||
std::cerr << "[Simulation] Created node " << node_count_ << " with IP "
|
||||
<< ((ip4 >> 24) & 0xFF) << "." << ((ip4 >> 16) & 0xFF) << "."
|
||||
<< ((ip4 >> 8) & 0xFF) << "." << (ip4 & 0xFF) << std::endl;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// --- SimulatedNode ---
|
||||
|
||||
SimulatedNode::SimulatedNode(Simulation &sim, uint32_t node_id)
|
||||
: sim_(sim)
|
||||
, network_(std::make_unique<FakeNetworkStack>(sim.net(), make_node_ip(node_id)))
|
||||
, random_(std::make_unique<FakeRandom>(12345 + node_id)) // Unique seed
|
||||
, memory_(std::make_unique<FakeMemory>())
|
||||
, c_network(network_->get_c_network())
|
||||
, c_random(random_->get_c_random())
|
||||
, c_memory(memory_->get_c_memory())
|
||||
, ip(make_node_ip(node_id))
|
||||
{
|
||||
}
|
||||
|
||||
SimulatedNode::~SimulatedNode() = default;
|
||||
|
||||
NetworkSystem &SimulatedNode::network() { return *network_; }
|
||||
ClockSystem &SimulatedNode::clock() { return sim_.clock(); }
|
||||
RandomSystem &SimulatedNode::random() { return *random_; }
|
||||
MemorySystem &SimulatedNode::memory() { return *memory_; }
|
||||
|
||||
SimulatedNode::ToxPtr SimulatedNode::create_tox(const Tox_Options *options)
|
||||
{
|
||||
std::unique_ptr<Tox_Options, decltype(&tox_options_free)> default_options(
|
||||
nullptr, tox_options_free);
|
||||
|
||||
if (options == nullptr) {
|
||||
default_options.reset(tox_options_new(nullptr));
|
||||
assert(default_options != nullptr);
|
||||
tox_options_set_ipv6_enabled(default_options.get(), false);
|
||||
tox_options_set_start_port(default_options.get(), 33445);
|
||||
tox_options_set_end_port(default_options.get(), 55555);
|
||||
options = default_options.get();
|
||||
}
|
||||
|
||||
Tox_Options_Testing opts_testing;
|
||||
Tox_System system;
|
||||
system.ns = &c_network;
|
||||
system.rng = &c_random;
|
||||
system.mem = &c_memory;
|
||||
system.mono_time_callback = [](void *user_data) -> uint64_t {
|
||||
return static_cast<FakeClock *>(user_data)->current_time_ms();
|
||||
};
|
||||
system.mono_time_user_data = &sim_.clock();
|
||||
|
||||
opts_testing.operating_system = &system;
|
||||
|
||||
Tox_Err_New err;
|
||||
Tox_Err_New_Testing err_testing;
|
||||
|
||||
Tox *t = tox_new_testing(options, &err, &opts_testing, &err_testing);
|
||||
|
||||
if (!t) {
|
||||
return nullptr;
|
||||
}
|
||||
return ToxPtr(t);
|
||||
}
|
||||
|
||||
FakeUdpSocket *SimulatedNode::get_primary_socket()
|
||||
{
|
||||
auto sockets = network_->get_bound_udp_sockets();
|
||||
if (sockets.empty())
|
||||
return nullptr;
|
||||
return sockets.front(); // Return the first one bound
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
330
testing/support/src/tox_network.cc
Normal file
330
testing/support/src/tox_network.cc
Normal file
@@ -0,0 +1,330 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright © 2026 The TokTok team.
|
||||
*/
|
||||
|
||||
#include "../public/tox_network.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../../toxcore/network.h"
|
||||
#include "../../../toxcore/tox.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
ConnectedFriend::~ConnectedFriend() = default;
|
||||
|
||||
std::vector<ConnectedFriend> setup_connected_friends(Simulation &sim, Tox *main_tox,
|
||||
SimulatedNode &main_node, int num_friends, const Tox_Options *options)
|
||||
{
|
||||
std::vector<ConnectedFriend> friends;
|
||||
friends.reserve(num_friends);
|
||||
|
||||
uint8_t main_pk[TOX_PUBLIC_KEY_SIZE];
|
||||
tox_self_get_public_key(main_tox, main_pk);
|
||||
uint8_t main_dht_id[TOX_PUBLIC_KEY_SIZE];
|
||||
tox_self_get_dht_id(main_tox, main_dht_id);
|
||||
|
||||
FakeUdpSocket *main_socket = main_node.get_primary_socket();
|
||||
if (!main_socket) {
|
||||
return {};
|
||||
}
|
||||
uint16_t main_port = main_socket->local_port();
|
||||
|
||||
char main_ip_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&main_node.ip, main_ip_str, sizeof(main_ip_str));
|
||||
|
||||
uint8_t prev_dht_id[TOX_PUBLIC_KEY_SIZE];
|
||||
memcpy(prev_dht_id, main_dht_id, TOX_PUBLIC_KEY_SIZE);
|
||||
char prev_ip_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&main_node.ip, prev_ip_str, sizeof(prev_ip_str));
|
||||
uint16_t prev_port = main_port;
|
||||
|
||||
for (int i = 0; i < num_friends; ++i) {
|
||||
auto node = sim.create_node();
|
||||
auto tox = node->create_tox(options);
|
||||
if (!tox) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint8_t friend_pk[TOX_PUBLIC_KEY_SIZE];
|
||||
tox_self_get_public_key(tox.get(), friend_pk);
|
||||
|
||||
Tox_Err_Friend_Add err;
|
||||
uint32_t fn = tox_friend_add_norequest(main_tox, friend_pk, &err);
|
||||
if (fn == UINT32_MAX || err != TOX_ERR_FRIEND_ADD_OK) {
|
||||
return {};
|
||||
}
|
||||
if (tox_friend_add_norequest(tox.get(), main_pk, &err) == UINT32_MAX
|
||||
|| err != TOX_ERR_FRIEND_ADD_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Bootstrap to the main node AND the PREVIOUS node in the chain
|
||||
tox_bootstrap(tox.get(), main_ip_str, main_port, main_dht_id, nullptr);
|
||||
if (i > 0) {
|
||||
tox_bootstrap(tox.get(), prev_ip_str, prev_port, prev_dht_id, nullptr);
|
||||
}
|
||||
|
||||
// Update prev for next node
|
||||
tox_self_get_dht_id(tox.get(), prev_dht_id);
|
||||
ip_parse_addr(&node->ip, prev_ip_str, sizeof(prev_ip_str));
|
||||
|
||||
FakeUdpSocket *node_socket = node->get_primary_socket();
|
||||
if (!node_socket) {
|
||||
return {};
|
||||
}
|
||||
prev_port = node_socket->local_port();
|
||||
|
||||
friends.push_back({std::move(node), std::move(tox), fn});
|
||||
|
||||
// Run simulation to let DHT stabilize
|
||||
sim.run_until(
|
||||
[&]() {
|
||||
tox_iterate(main_tox, nullptr);
|
||||
for (auto &f : friends) {
|
||||
tox_iterate(f.tox.get(), nullptr);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
200);
|
||||
}
|
||||
|
||||
// Optional: Bootstrap main_tox to the last node to complete the circle
|
||||
if (!friends.empty()) {
|
||||
tox_bootstrap(main_tox, prev_ip_str, prev_port, prev_dht_id, nullptr);
|
||||
}
|
||||
|
||||
// Run simulation until all are connected
|
||||
sim.run_until(
|
||||
[&]() {
|
||||
bool all_connected = true;
|
||||
int connected_count = 0;
|
||||
tox_iterate(main_tox, nullptr);
|
||||
for (auto &f : friends) {
|
||||
tox_iterate(f.tox.get(), nullptr);
|
||||
if (tox_friend_get_connection_status(main_tox, f.friend_number, nullptr)
|
||||
!= TOX_CONNECTION_NONE
|
||||
&& tox_friend_get_connection_status(f.tox.get(), 0, nullptr)
|
||||
!= TOX_CONNECTION_NONE) {
|
||||
connected_count++;
|
||||
} else {
|
||||
all_connected = false;
|
||||
}
|
||||
}
|
||||
static uint64_t last_print = 0;
|
||||
if (sim.clock().current_time_ms() - last_print > 1000) {
|
||||
std::cerr << "[setup_connected_friends] Friends connected: " << connected_count
|
||||
<< "/" << friends.size() << " (time: " << sim.clock().current_time_ms()
|
||||
<< "ms)" << std::endl;
|
||||
|
||||
if (connected_count < static_cast<int>(friends.size())
|
||||
&& sim.clock().current_time_ms() > 10000) {
|
||||
for (size_t i = 0; i < friends.size(); ++i) {
|
||||
auto s1 = tox_friend_get_connection_status(
|
||||
main_tox, friends[i].friend_number, nullptr);
|
||||
auto s2
|
||||
= tox_friend_get_connection_status(friends[i].tox.get(), 0, nullptr);
|
||||
if (s1 == TOX_CONNECTION_NONE || s2 == TOX_CONNECTION_NONE) {
|
||||
std::cerr << " Friend " << i << " not connected (Main->F: " << s1
|
||||
<< ", F->Main: " << s2 << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_print = sim.clock().current_time_ms();
|
||||
}
|
||||
return all_connected;
|
||||
},
|
||||
300000); // 5 minutes simulation time for 100 nodes to converge
|
||||
|
||||
return friends;
|
||||
}
|
||||
|
||||
bool connect_friends(
|
||||
Simulation &sim, SimulatedNode &node1, Tox *tox1, SimulatedNode &node2, Tox *tox2)
|
||||
{
|
||||
uint8_t pk1[TOX_PUBLIC_KEY_SIZE];
|
||||
uint8_t pk2[TOX_PUBLIC_KEY_SIZE];
|
||||
tox_self_get_public_key(tox1, pk1);
|
||||
tox_self_get_public_key(tox2, pk2);
|
||||
|
||||
Tox_Err_Friend_Add err_add;
|
||||
uint32_t f1 = tox_friend_add_norequest(tox1, pk2, &err_add);
|
||||
if (f1 == UINT32_MAX || err_add != TOX_ERR_FRIEND_ADD_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t f2 = tox_friend_add_norequest(tox2, pk1, &err_add);
|
||||
if (f2 == UINT32_MAX || err_add != TOX_ERR_FRIEND_ADD_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t dht_id1[TOX_PUBLIC_KEY_SIZE];
|
||||
tox_self_get_dht_id(tox1, dht_id1);
|
||||
char ip1_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&node1.ip, ip1_str, sizeof(ip1_str));
|
||||
|
||||
FakeUdpSocket *s1 = node1.get_primary_socket();
|
||||
if (!s1) {
|
||||
return false;
|
||||
}
|
||||
uint16_t port1 = s1->local_port();
|
||||
|
||||
uint8_t dht_id2[TOX_PUBLIC_KEY_SIZE];
|
||||
tox_self_get_dht_id(tox2, dht_id2);
|
||||
char ip2_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&node2.ip, ip2_str, sizeof(ip2_str));
|
||||
|
||||
FakeUdpSocket *s2 = node2.get_primary_socket();
|
||||
if (!s2) {
|
||||
return false;
|
||||
}
|
||||
uint16_t port2 = s2->local_port();
|
||||
|
||||
tox_bootstrap(tox1, ip2_str, port2, dht_id2, nullptr);
|
||||
tox_bootstrap(tox2, ip1_str, port1, dht_id1, nullptr);
|
||||
|
||||
sim.run_until(
|
||||
[&]() {
|
||||
tox_iterate(tox1, nullptr);
|
||||
tox_iterate(tox2, nullptr);
|
||||
return tox_friend_get_connection_status(tox1, f1, nullptr) != TOX_CONNECTION_NONE
|
||||
&& tox_friend_get_connection_status(tox2, f2, nullptr) != TOX_CONNECTION_NONE;
|
||||
},
|
||||
60000);
|
||||
|
||||
return tox_friend_get_connection_status(tox1, f1, nullptr) != TOX_CONNECTION_NONE
|
||||
&& tox_friend_get_connection_status(tox2, f2, nullptr) != TOX_CONNECTION_NONE;
|
||||
}
|
||||
|
||||
uint32_t setup_connected_group(
|
||||
Simulation &sim, Tox *main_tox, const std::vector<ConnectedFriend> &friends)
|
||||
{
|
||||
struct NodeGroupState {
|
||||
uint32_t peer_count = 0;
|
||||
uint32_t group_number = UINT32_MAX;
|
||||
};
|
||||
|
||||
NodeGroupState main_state;
|
||||
tox_callback_group_peer_join(main_tox, [](Tox *, uint32_t, uint32_t, void *user_data) {
|
||||
static_cast<NodeGroupState *>(user_data)->peer_count++;
|
||||
});
|
||||
|
||||
Tox_Err_Group_New err_new;
|
||||
main_state.group_number = tox_group_new(main_tox, TOX_GROUP_PRIVACY_STATE_PUBLIC,
|
||||
reinterpret_cast<const uint8_t *>("test"), 4, reinterpret_cast<const uint8_t *>("main"), 4,
|
||||
&err_new);
|
||||
|
||||
if (main_state.group_number == UINT32_MAX || err_new != TOX_ERR_GROUP_NEW_OK) {
|
||||
std::cerr << "tox_group_new failed with error: " << err_new << std::endl;
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<NodeGroupState>> friend_states;
|
||||
friend_states.reserve(friends.size());
|
||||
|
||||
for (size_t i = 0; i < friends.size(); ++i) {
|
||||
auto state = std::make_unique<NodeGroupState>();
|
||||
tox_callback_group_peer_join(
|
||||
friends[i].tox.get(), [](Tox *, uint32_t, uint32_t, void *user_data) {
|
||||
static_cast<NodeGroupState *>(user_data)->peer_count++;
|
||||
});
|
||||
|
||||
tox_callback_group_invite(friends[i].tox.get(),
|
||||
[](Tox *tox, uint32_t friend_number, const uint8_t *invite_data,
|
||||
size_t invite_data_length, const uint8_t *, size_t, void *user_data) {
|
||||
NodeGroupState *ng_state = static_cast<NodeGroupState *>(user_data);
|
||||
Tox_Err_Group_Invite_Accept err_accept;
|
||||
ng_state->group_number
|
||||
= tox_group_invite_accept(tox, friend_number, invite_data, invite_data_length,
|
||||
reinterpret_cast<const uint8_t *>("peer"), 4, nullptr, 0, &err_accept);
|
||||
if (ng_state->group_number == UINT32_MAX
|
||||
|| err_accept != TOX_ERR_GROUP_INVITE_ACCEPT_OK) {
|
||||
ng_state->group_number = UINT32_MAX;
|
||||
}
|
||||
});
|
||||
|
||||
friend_states.push_back(std::move(state));
|
||||
}
|
||||
|
||||
// Run until all have joined and see everyone
|
||||
bool success = false;
|
||||
uint64_t last_print = 0;
|
||||
size_t invites_sent = 0;
|
||||
|
||||
sim.run_until(
|
||||
[&]() {
|
||||
tox_iterate(main_tox, &main_state);
|
||||
|
||||
// Throttle invites: keep max 5 pending
|
||||
size_t accepted_count = 0;
|
||||
for (size_t k = 0; k < invites_sent; ++k) {
|
||||
if (friend_states[k]->group_number != UINT32_MAX) {
|
||||
accepted_count++;
|
||||
}
|
||||
}
|
||||
|
||||
while (invites_sent < friends.size() && (invites_sent - accepted_count) < 5) {
|
||||
Tox_Err_Group_Invite_Friend err_invite;
|
||||
if (tox_group_invite_friend(main_tox, main_state.group_number,
|
||||
friends[invites_sent].friend_number, &err_invite)) {
|
||||
invites_sent++;
|
||||
} else {
|
||||
if (err_invite != TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND) {
|
||||
std::cerr << "Invite failed for friend " << invites_sent << ": "
|
||||
<< err_invite << std::endl;
|
||||
}
|
||||
break; // Stop trying to send for this tick if we failed
|
||||
}
|
||||
}
|
||||
|
||||
bool all_see_all = true;
|
||||
|
||||
if (main_state.peer_count < friends.size()) {
|
||||
all_see_all = false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < friends.size(); ++i) {
|
||||
tox_iterate(friends[i].tox.get(), friend_states[i].get());
|
||||
if (friend_states[i]->group_number == UINT32_MAX
|
||||
|| friend_states[i]->peer_count < friends.size()) {
|
||||
all_see_all = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((sim.clock().current_time_ms() - last_print) % 5000 == 0) {
|
||||
int joined = 0;
|
||||
int fully_connected = 0;
|
||||
if (main_state.group_number != UINT32_MAX)
|
||||
joined++;
|
||||
if (main_state.peer_count >= friends.size())
|
||||
fully_connected++;
|
||||
|
||||
for (const auto &fs : friend_states) {
|
||||
if (fs->group_number != UINT32_MAX) {
|
||||
joined++;
|
||||
if (fs->peer_count >= friends.size())
|
||||
fully_connected++;
|
||||
}
|
||||
}
|
||||
std::cerr << "[setup_connected_group] Main peer count: " << main_state.peer_count
|
||||
<< "/" << friends.size() << ", Nodes joined: " << joined << "/"
|
||||
<< (friends.size() + 1) << ", fully connected: " << fully_connected << "/"
|
||||
<< (friends.size() + 1) << " (time: " << sim.clock().current_time_ms()
|
||||
<< "ms)" << std::endl;
|
||||
last_print = sim.clock().current_time_ms();
|
||||
}
|
||||
|
||||
if (all_see_all) {
|
||||
success = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
300000); // 5 minutes
|
||||
|
||||
return success ? main_state.group_number : UINT32_MAX;
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
226
testing/support/tox_network_test.cc
Normal file
226
testing/support/tox_network_test.cc
Normal file
@@ -0,0 +1,226 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Copyright © 2026 The TokTok team.
|
||||
*/
|
||||
|
||||
#include "public/tox_network.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
TEST(ToxNetworkTest, SetupConnectedFriends)
|
||||
{
|
||||
Simulation sim;
|
||||
sim.net().set_latency(5);
|
||||
auto main_node = sim.create_node();
|
||||
auto main_tox = main_node->create_tox();
|
||||
ASSERT_NE(main_tox, nullptr);
|
||||
|
||||
const int num_friends = 3;
|
||||
auto friends = setup_connected_friends(sim, main_tox.get(), *main_node, num_friends);
|
||||
|
||||
ASSERT_EQ(friends.size(), num_friends);
|
||||
|
||||
for (const auto &f : friends) {
|
||||
EXPECT_NE(tox_friend_get_connection_status(main_tox.get(), f.friend_number, nullptr),
|
||||
TOX_CONNECTION_NONE);
|
||||
}
|
||||
|
||||
// Verify they can actually communicate
|
||||
struct Context {
|
||||
int count = 0;
|
||||
} ctx;
|
||||
|
||||
tox_callback_friend_message(main_tox.get(),
|
||||
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) {
|
||||
static_cast<Context *>(user_data)->count++;
|
||||
});
|
||||
|
||||
for (const auto &f : friends) {
|
||||
const uint8_t msg[] = "hello";
|
||||
tox_friend_send_message(
|
||||
f.tox.get(), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr);
|
||||
}
|
||||
|
||||
sim.run_until([&]() {
|
||||
tox_iterate(main_tox.get(), &ctx);
|
||||
for (auto &f : friends) {
|
||||
tox_iterate(f.tox.get(), nullptr);
|
||||
}
|
||||
return ctx.count == num_friends;
|
||||
});
|
||||
|
||||
EXPECT_EQ(ctx.count, num_friends);
|
||||
}
|
||||
|
||||
TEST(ToxNetworkTest, Setup50ConnectedFriends)
|
||||
{
|
||||
Simulation sim;
|
||||
sim.net().set_latency(5);
|
||||
auto main_node = sim.create_node();
|
||||
auto main_tox = main_node->create_tox();
|
||||
ASSERT_NE(main_tox, nullptr);
|
||||
|
||||
const int num_friends = 50;
|
||||
auto friends = setup_connected_friends(sim, main_tox.get(), *main_node, num_friends);
|
||||
|
||||
ASSERT_EQ(friends.size(), num_friends);
|
||||
|
||||
struct Context {
|
||||
int count = 0;
|
||||
} ctx;
|
||||
|
||||
tox_callback_friend_message(main_tox.get(),
|
||||
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) {
|
||||
static_cast<Context *>(user_data)->count++;
|
||||
});
|
||||
|
||||
for (const auto &f : friends) {
|
||||
const uint8_t msg[] = "hello";
|
||||
tox_friend_send_message(
|
||||
f.tox.get(), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr);
|
||||
}
|
||||
|
||||
sim.run_until(
|
||||
[&]() {
|
||||
tox_iterate(main_tox.get(), &ctx);
|
||||
for (auto &f : friends) {
|
||||
tox_iterate(f.tox.get(), nullptr);
|
||||
}
|
||||
return ctx.count == num_friends;
|
||||
},
|
||||
60000);
|
||||
|
||||
EXPECT_EQ(ctx.count, num_friends);
|
||||
}
|
||||
|
||||
TEST(ToxNetworkTest, ConnectFriends)
|
||||
{
|
||||
Simulation sim;
|
||||
sim.net().set_latency(5);
|
||||
auto node1 = sim.create_node();
|
||||
auto tox1 = node1->create_tox();
|
||||
auto node2 = sim.create_node();
|
||||
auto tox2 = node2->create_tox();
|
||||
|
||||
ASSERT_NE(tox1, nullptr);
|
||||
ASSERT_NE(tox2, nullptr);
|
||||
|
||||
ASSERT_TRUE(connect_friends(sim, *node1, tox1.get(), *node2, tox2.get()));
|
||||
|
||||
EXPECT_NE(tox_friend_get_connection_status(tox1.get(), 0, nullptr), TOX_CONNECTION_NONE);
|
||||
EXPECT_NE(tox_friend_get_connection_status(tox2.get(), 0, nullptr), TOX_CONNECTION_NONE);
|
||||
|
||||
// Verify communication
|
||||
bool received = false;
|
||||
tox_callback_friend_message(tox2.get(),
|
||||
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) {
|
||||
*static_cast<bool *>(user_data) = true;
|
||||
});
|
||||
|
||||
const uint8_t msg[] = "hello";
|
||||
tox_friend_send_message(tox1.get(), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr);
|
||||
|
||||
sim.run_until([&]() {
|
||||
tox_iterate(tox1.get(), nullptr);
|
||||
tox_iterate(tox2.get(), &received);
|
||||
return received;
|
||||
});
|
||||
|
||||
EXPECT_TRUE(received);
|
||||
}
|
||||
|
||||
TEST(ToxNetworkTest, SetupConnectedGroup)
|
||||
{
|
||||
Simulation sim;
|
||||
sim.net().set_latency(5);
|
||||
auto main_node = sim.create_node();
|
||||
auto main_tox = main_node->create_tox();
|
||||
ASSERT_NE(main_tox, nullptr);
|
||||
|
||||
const int num_friends = 15;
|
||||
auto friends = setup_connected_friends(sim, main_tox.get(), *main_node, num_friends);
|
||||
ASSERT_EQ(friends.size(), num_friends);
|
||||
|
||||
uint32_t group_number = setup_connected_group(sim, main_tox.get(), friends);
|
||||
EXPECT_NE(group_number, UINT32_MAX);
|
||||
|
||||
// Verify we can send a group message
|
||||
struct Context {
|
||||
int count = 0;
|
||||
} ctx;
|
||||
|
||||
tox_callback_group_message(main_tox.get(),
|
||||
[](Tox *, uint32_t, uint32_t, Tox_Message_Type, const uint8_t *, size_t, uint32_t,
|
||||
void *user_data) { static_cast<Context *>(user_data)->count++; });
|
||||
|
||||
for (const auto &f : friends) {
|
||||
const uint8_t msg[] = "hello";
|
||||
uint32_t f_gn = 0; // It should be 0 since it's the first group.
|
||||
Tox_Err_Group_Send_Message err_send;
|
||||
tox_group_send_message(
|
||||
f.tox.get(), f_gn, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err_send);
|
||||
EXPECT_EQ(err_send, TOX_ERR_GROUP_SEND_MESSAGE_OK);
|
||||
}
|
||||
|
||||
sim.run_until(
|
||||
[&]() {
|
||||
tox_iterate(main_tox.get(), &ctx);
|
||||
for (auto &f : friends) {
|
||||
tox_iterate(f.tox.get(), nullptr);
|
||||
}
|
||||
return ctx.count == num_friends;
|
||||
},
|
||||
10000);
|
||||
|
||||
EXPECT_EQ(ctx.count, num_friends);
|
||||
}
|
||||
|
||||
TEST(ToxNetworkTest, Setup50ConnectedGroup)
|
||||
{
|
||||
Simulation sim;
|
||||
sim.net().set_latency(5);
|
||||
auto main_node = sim.create_node();
|
||||
auto main_tox = main_node->create_tox();
|
||||
ASSERT_NE(main_tox, nullptr);
|
||||
|
||||
const int num_friends = 50;
|
||||
auto friends = setup_connected_friends(sim, main_tox.get(), *main_node, num_friends);
|
||||
ASSERT_EQ(friends.size(), num_friends);
|
||||
|
||||
uint32_t group_number = setup_connected_group(sim, main_tox.get(), friends);
|
||||
EXPECT_NE(group_number, UINT32_MAX);
|
||||
|
||||
struct Context {
|
||||
int count = 0;
|
||||
} ctx;
|
||||
|
||||
tox_callback_group_message(main_tox.get(),
|
||||
[](Tox *, uint32_t, uint32_t, Tox_Message_Type, const uint8_t *, size_t, uint32_t,
|
||||
void *user_data) { static_cast<Context *>(user_data)->count++; });
|
||||
|
||||
for (const auto &f : friends) {
|
||||
const uint8_t msg[] = "hello";
|
||||
uint32_t f_gn = 0;
|
||||
Tox_Err_Group_Send_Message err_send;
|
||||
tox_group_send_message(
|
||||
f.tox.get(), f_gn, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err_send);
|
||||
EXPECT_EQ(err_send, TOX_ERR_GROUP_SEND_MESSAGE_OK);
|
||||
}
|
||||
|
||||
sim.run_until(
|
||||
[&]() {
|
||||
tox_iterate(main_tox.get(), &ctx);
|
||||
for (auto &f : friends) {
|
||||
tox_iterate(f.tox.get(), nullptr);
|
||||
}
|
||||
return ctx.count == num_friends;
|
||||
},
|
||||
120000);
|
||||
|
||||
EXPECT_EQ(ctx.count, num_friends);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
Reference in New Issue
Block a user