Squashed 'external/toxcore/c-toxcore/' changes from c9cdae001..9ed2fa80d

9ed2fa80d fix(toxav): remove extra copy of video frame on encode
de30cf3ad docs: Add new file kinds, that should be useful to all clients.
d5b5e879d fix(DHT): Correct node skipping logic timed out nodes.
30e71fe97 refactor: Generate event dispatch functions and add tox_events_dispatch.
8fdbb0b50 style: Format parameter lists in event handlers.
d00dee12b refactor: Add warning logs when losing chat invites.
b144e8db1 feat: Add a way to look up a file number by ID.
849281ea0 feat: Add a way to fetch groups by chat ID.
a2c177396 refactor: Harden event system and improve type safety.
8f5caa656 refactor: Add MessagePack string support to bin_pack.
34e8d5ad5 chore: Add GitHub CodeQL workflow and local Docker runner.
f7b068010 refactor: Add nullability annotations to event headers.
788abe651 refactor(toxav): Use system allocator for mutexes.
2e4b423eb refactor: Use specific typedefs for public API arrays.
2baf34775 docs(toxav): update idle iteration interval see 679444751876fa3882a717772918ebdc8f083354
2f87ac67b feat: Add Event Loop abstraction (Ev).
f8dfc38d8 test: Fix data race in ToxScenario virtual_clock.
38313921e test(TCP): Add regression test for TCP priority queue integrity.
f94a50d9a refactor(toxav): Replace mutable_mutex with dynamically allocated mutex.
ad054511e refactor: Internalize DHT structs and add debug helpers.
8b467cc96 fix: Prevent potential integer overflow in group chat handshake.
4962bdbb8 test: Improve TCP simulation and add tests
5f0227093 refactor: Allow nullable data in group chat handlers.
e97b18ea9 chore: Improve Windows Docker support.
b14943bbd refactor: Move Logger out of Messenger into Tox.
dd3136250 cleanup: Apply nullability qualifiers to C++ codebase.
1849f70fc refactor: Extract low-level networking code to net and os_network.
8fec75421 refactor: Delete tox_random, align on rng and os_random.
a03ae8051 refactor: Delete tox_memory, align on mem and os_memory.
4c88fed2c refactor: Use `std::` prefixes more consistently in C++ code.
72452f2ae test: Add some more tests for onion and shared key cache.
d5a51b09a cleanup: Use tox_attributes.h in tox_private.h and install it.
b6f5b9fc5 test: Add some benchmarks for various high level things.
8a8d02785 test(support): Introduce threaded Tox runner and simulation barrier
d68d1d095 perf(toxav): optimize audio and video intermediate buffers by keeping them around
REVERT: c9cdae001 fix(toxav): remove extra copy of video frame on encode

git-subtree-dir: external/toxcore/c-toxcore
git-subtree-split: 9ed2fa80d582c714d6bdde6a7648220a92cddff8
This commit is contained in:
Green Sky
2026-02-01 14:26:52 +01:00
parent 565efa4f39
commit 9b36dd9d99
274 changed files with 11891 additions and 4292 deletions

View File

@@ -0,0 +1,479 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2026 The TokTok team.
*/
#include <benchmark/benchmark.h>
#include <iostream>
#include <memory>
#include <vector>
#include "../../testing/support/public/simulation.hh"
#include "../../testing/support/public/tox_network.hh"
#include "../../toxcore/tox.h"
namespace {
using tox::test::ConnectedFriend;
using tox::test::setup_connected_friends;
using tox::test::SimulatedNode;
using tox::test::Simulation;
// --- Helper Contexts ---
struct GroupContext {
uint32_t peer_count = 0;
uint32_t group_number = UINT32_MAX;
};
// --- Fixtures ---
class ToxIterateScalingFixture : public benchmark::Fixture {
public:
void SetUp(benchmark::State &state) override
{
// Explicitly clear members to handle fixture reuse or non-empty initial state.
// Order matters: dependent objects first.
friend_toxes.clear();
friend_nodes.clear();
main_tox.reset();
main_node.reset();
sim.reset();
int num_friends = state.range(0);
sim = std::make_unique<Simulation>();
main_node = sim->create_node();
main_tox = main_node->create_tox();
for (int i = 0; i < num_friends; ++i) {
auto node = sim->create_node();
auto tox = node->create_tox();
uint8_t friend_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox.get(), friend_pk);
Tox_Err_Friend_Add err;
tox_friend_add_norequest(main_tox.get(), friend_pk, &err);
friend_nodes.push_back(std::move(node));
friend_toxes.push_back(std::move(tox));
}
}
protected:
std::unique_ptr<Simulation> sim;
std::unique_ptr<SimulatedNode> main_node;
SimulatedNode::ToxPtr main_tox;
std::vector<std::unique_ptr<SimulatedNode>> friend_nodes;
std::vector<SimulatedNode::ToxPtr> friend_toxes;
};
// --- Contexts for Shared State Benchmarks ---
class ToxOnlineDisconnectedScalingFixture : public benchmark::Fixture {
static constexpr bool kVerbose = false;
public:
void SetUp(benchmark::State &state) override
{
// Explicitly clear members to handle fixture reuse or non-empty initial state.
// Order matters: dependent objects first (Tox depends on Node).
main_tox.reset();
bootstrap_tox.reset();
main_node.reset();
bootstrap_node.reset();
sim.reset();
int num_friends = state.range(0);
sim = std::make_unique<Simulation>();
sim->net().set_latency(1); // Low latency to encourage traffic
sim->net().set_verbose(kVerbose);
// Create a bootstrap node to ensure we are "online" on the DHT
bootstrap_node = sim->create_node();
auto log_cb = [](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line,
const char *func, const char *message, void *user_data) {
if (kVerbose) {
std::cerr << "Log: " << file << ":" << line << " (" << func << ") " << message
<< std::endl;
}
};
auto opts_bs = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
assert(opts_bs);
tox_options_set_local_discovery_enabled(opts_bs.get(), false);
tox_options_set_ipv6_enabled(opts_bs.get(), false);
tox_options_set_log_callback(opts_bs.get(), log_cb);
bootstrap_tox = bootstrap_node->create_tox(opts_bs.get());
uint8_t bootstrap_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(bootstrap_tox.get(), bootstrap_pk);
uint16_t bootstrap_port = bootstrap_node->get_primary_socket()->local_port();
main_node = sim->create_node();
auto opts = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
assert(opts);
// Disable local discovery to force DHT usage
tox_options_set_local_discovery_enabled(opts.get(), false);
tox_options_set_ipv6_enabled(opts.get(), false);
tox_options_set_log_callback(opts.get(), log_cb);
main_tox = main_node->create_tox(opts.get());
// Bootstrap to the network (Mutual bootstrap to ensure connectivity)
Ip_Ntoa bs_ip_str_buf;
const char *bs_ip_str = net_ip_ntoa(&bootstrap_node->ip, &bs_ip_str_buf);
Tox_Err_Bootstrap bs_err;
tox_bootstrap(main_tox.get(), bs_ip_str, bootstrap_port, bootstrap_pk, &bs_err);
if (bs_err != TOX_ERR_BOOTSTRAP_OK) {
std::cerr << "bootstrapping failed: " << bs_err << "\n";
std::abort();
}
// Run until we are connected to the DHT
sim->run_until(
[&]() {
tox_iterate(main_tox.get(), nullptr);
tox_iterate(bootstrap_tox.get(), nullptr);
return tox_self_get_connection_status(main_tox.get()) != TOX_CONNECTION_NONE;
},
15000);
if (tox_self_get_connection_status(main_tox.get()) == TOX_CONNECTION_NONE) {
std::cerr << "WARNING: Failed to connect to DHT in SetUp (timeout 30s)\n";
std::abort();
}
for (int i = 0; i < num_friends; ++i) {
// Add friend but don't create a node for them -> they are offline
uint8_t friend_pk[TOX_PUBLIC_KEY_SIZE];
// Just generate a random PK
main_node->fake_random().bytes(friend_pk, TOX_PUBLIC_KEY_SIZE);
Tox_Err_Friend_Add err;
tox_friend_add_norequest(main_tox.get(), friend_pk, &err);
}
}
protected:
std::unique_ptr<Simulation> sim;
std::unique_ptr<SimulatedNode> main_node;
SimulatedNode::ToxPtr main_tox;
std::unique_ptr<SimulatedNode> bootstrap_node;
SimulatedNode::ToxPtr bootstrap_tox;
};
BENCHMARK_DEFINE_F(ToxOnlineDisconnectedScalingFixture, Iterate)(benchmark::State &state)
{
if (tox_self_get_connection_status(main_tox.get()) == TOX_CONNECTION_NONE) {
state.SkipWithError("not connected to DHT");
}
for (auto _ : state) {
tox_iterate(main_tox.get(), nullptr);
tox_iterate(bootstrap_tox.get(), nullptr);
uint32_t interval = tox_iteration_interval(main_tox.get());
uint32_t interval_bs = tox_iteration_interval(bootstrap_tox.get());
sim->advance_time(std::min(interval, interval_bs));
}
state.counters["mem_current"]
= benchmark::Counter(static_cast<double>(main_node->fake_memory().current_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
}
BENCHMARK_REGISTER_F(ToxOnlineDisconnectedScalingFixture, Iterate)
->Arg(0)
->Arg(10)
->Arg(100)
->Arg(1000)
->Arg(2000);
struct ConnectedContext {
std::unique_ptr<Simulation> sim;
std::unique_ptr<SimulatedNode> main_node;
SimulatedNode::ToxPtr main_tox;
std::vector<ConnectedFriend> friends;
int num_friends = -1;
void Setup(int n)
{
if (num_friends == n)
return;
// Destruction order is critical
friends.clear();
main_tox.reset();
main_node.reset();
sim.reset();
sim = std::make_unique<Simulation>();
sim->net().set_latency(5);
main_node = sim->create_node();
main_tox = main_node->create_tox();
num_friends = n;
if (n > 0) {
friends = setup_connected_friends(*sim, main_tox.get(), *main_node, num_friends);
}
}
~ConnectedContext();
};
ConnectedContext::~ConnectedContext() = default;
struct GroupScalingContext {
static constexpr bool verbose = false;
std::unique_ptr<Simulation> sim;
std::unique_ptr<SimulatedNode> main_node;
SimulatedNode::ToxPtr main_tox;
GroupContext main_ctx;
std::vector<ConnectedFriend> friends;
int num_peers = -1;
void Setup(int peers)
{
if (num_peers == peers)
return;
// Destruction order is critical
friends.clear();
main_ctx = GroupContext();
main_tox.reset();
main_node.reset();
sim.reset();
sim = std::make_unique<Simulation>();
sim->net().set_latency(5);
main_node = sim->create_node();
auto opts = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
tox_options_set_ipv6_enabled(opts.get(), false);
tox_options_set_local_discovery_enabled(opts.get(), false);
main_tox = main_node->create_tox(opts.get());
num_peers = peers;
// Setup Group Callbacks
tox_callback_group_peer_join(
main_tox.get(), [](Tox *, uint32_t, uint32_t, void *user_data) {
static_cast<GroupContext *>(user_data)->peer_count++;
});
tox_callback_group_peer_exit(main_tox.get(),
[](Tox *, uint32_t, uint32_t, Tox_Group_Exit_Type, const uint8_t *, size_t,
const uint8_t *, size_t,
void *user_data) { static_cast<GroupContext *>(user_data)->peer_count--; });
Tox_Err_Group_New err_new;
main_ctx.group_number = tox_group_new(main_tox.get(), TOX_GROUP_PRIVACY_STATE_PUBLIC,
reinterpret_cast<const uint8_t *>("test"), 4, reinterpret_cast<const uint8_t *>("main"),
4, &err_new);
if (num_peers > 0) {
// Setup Friends
auto opts_friends = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
tox_options_set_ipv6_enabled(opts_friends.get(), false);
tox_options_set_local_discovery_enabled(opts_friends.get(), false);
friends = setup_connected_friends(
*sim, main_tox.get(), *main_node, num_peers, opts_friends.get());
// Invite Friends
for (const auto &f : friends) {
tox_group_invite_friend(
main_tox.get(), main_ctx.group_number, f.friend_number, nullptr);
}
// Wait for Joins
std::vector<uint32_t> peer_group_numbers(num_peers, UINT32_MAX);
sim->run_until(
[&]() {
tox_iterate(main_tox.get(), &main_ctx);
// Poll events
for (size_t i = 0; i < friends.size(); ++i) {
auto batches = friends[i].runner->poll_events();
for (const auto &batch : batches) {
size_t size = tox_events_get_size(batch.get());
for (size_t k = 0; k < size; ++k) {
const Tox_Event *e = tox_events_get(batch.get(), k);
if (tox_event_get_type(e) == TOX_EVENT_GROUP_INVITE) {
auto *ev = tox_event_get_group_invite(e);
uint32_t friend_number
= tox_event_group_invite_get_friend_number(ev);
const uint8_t *data
= tox_event_group_invite_get_invite_data(ev);
size_t len = tox_event_group_invite_get_invite_data_length(ev);
std::vector<uint8_t> invite_data(data, data + len);
friends[i].runner->execute([=](Tox *tox) {
tox_group_invite_accept(tox, friend_number,
invite_data.data(), invite_data.size(),
reinterpret_cast<const uint8_t *>("peer"), 4, nullptr,
0, nullptr);
});
} else if (tox_event_get_type(e) == TOX_EVENT_GROUP_SELF_JOIN) {
auto *ev = tox_event_get_group_self_join(e);
peer_group_numbers[i]
= tox_event_group_self_join_get_group_number(ev);
}
}
}
}
bool all_joined = true;
for (auto gn : peer_group_numbers)
if (gn == UINT32_MAX)
all_joined = false;
return all_joined;
},
60000);
// Wait for Convergence
sim->run_until(
[&]() {
tox_iterate(main_tox.get(), &main_ctx);
if (main_ctx.peer_count >= static_cast<uint32_t>(num_peers))
return true;
static uint64_t last_print = 0;
if (verbose && sim->clock().current_time_ms() - last_print > 1000) {
std::cerr << "Peers joined: " << main_ctx.peer_count << "/" << num_peers
<< std::endl;
last_print = sim->clock().current_time_ms();
}
return false;
},
120000);
}
}
~GroupScalingContext();
};
GroupScalingContext::~GroupScalingContext() = default;
// --- Benchmark Definitions ---
BENCHMARK_DEFINE_F(ToxIterateScalingFixture, Iterate)(benchmark::State &state)
{
for (auto _ : state) {
tox_iterate(main_tox.get(), nullptr);
}
state.counters["mem_current"]
= benchmark::Counter(static_cast<double>(main_node->fake_memory().current_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
state.counters["mem_max"]
= benchmark::Counter(static_cast<double>(main_node->fake_memory().max_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
}
BENCHMARK_REGISTER_F(ToxIterateScalingFixture, Iterate)
->Arg(0)
->Arg(10)
->Arg(100)
->Arg(200)
->Arg(300);
void RunConnectedScaling(benchmark::State &state, ConnectedContext &ctx)
{
ctx.Setup(state.range(0));
for (auto _ : state) {
tox_iterate(ctx.main_tox.get(), nullptr);
}
state.counters["mem_current"]
= benchmark::Counter(static_cast<double>(ctx.main_node->fake_memory().current_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
state.counters["mem_max"]
= benchmark::Counter(static_cast<double>(ctx.main_node->fake_memory().max_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
}
void RunGroupScaling(benchmark::State &state, GroupScalingContext &ctx)
{
ctx.Setup(state.range(0));
for (auto _ : state) {
tox_iterate(ctx.main_tox.get(), &ctx.main_ctx);
}
state.counters["mem_current"]
= benchmark::Counter(static_cast<double>(ctx.main_node->fake_memory().current_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
state.counters["mem_max"]
= benchmark::Counter(static_cast<double>(ctx.main_node->fake_memory().max_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
state.counters["peers"] = benchmark::Counter(
static_cast<double>(ctx.main_ctx.peer_count + 1), benchmark::Counter::kDefaults);
}
/**
* @brief Benchmark the time and CPU required to discover and connect to many friends.
*
* This stresses the Onion Client's discovery mechanism (shared key caching)
* and the DHT's shared key cache efficiency.
*/
static void BM_MassDiscovery(benchmark::State &state)
{
const int num_friends = state.range(0);
for (auto _ : state) {
Simulation sim;
// Set a realistic latency to ensure packets are in flight and DHT/Onion logic
// has to run multiple iterations.
sim.net().set_latency(10);
auto alice_node = sim.create_node();
auto alice_tox = alice_node->create_tox();
// setup_connected_friends runs the simulation until all friends are connected.
auto friends = setup_connected_friends(sim, alice_tox.get(), *alice_node, num_friends);
benchmark::DoNotOptimize(friends);
}
}
BENCHMARK(BM_MassDiscovery)
->Arg(50)
->Arg(100)
->Arg(200)
->Unit(benchmark::kMillisecond)
->Iterations(5);
} // namespace
int main(int argc, char **argv)
{
::benchmark::Initialize(&argc, argv);
if (::benchmark::ReportUnrecognizedArguments(argc, argv)) {
return 1;
}
ConnectedContext connected_ctx;
benchmark::RegisterBenchmark("ToxConnectedScalingFixture/IterateConnected",
[&](benchmark::State &st) { RunConnectedScaling(st, connected_ctx); })
->Arg(0)
->Arg(10)
->Arg(20)
->Arg(50);
GroupScalingContext group_ctx;
benchmark::RegisterBenchmark("ToxGroupScalingFixture/IterateGroup",
[&](benchmark::State &st) { RunGroupScaling(st, group_ctx); })
->Arg(0)
->Arg(10)
->Arg(20)
->Arg(50);
::benchmark::RunSpecifiedBenchmarks();
::benchmark::Shutdown();
return 0;
}