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:
Green Sky
2026-01-11 14:42:31 +01:00
parent e95f2cbb1c
commit 565efa4f39
328 changed files with 19057 additions and 13982 deletions

View File

@@ -0,0 +1,29 @@
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
cc_library(
name = "scenario_framework",
testonly = True,
srcs = ["framework/framework.c"],
hdrs = ["framework/framework.h"],
visibility = ["//visibility:public"],
deps = [
"//c-toxcore/testing:misc_tools",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_dispatch",
"//c-toxcore/toxcore:tox_events",
],
)
[cc_test(
name = src[len("scenario_"):-2],
size = "small",
srcs = [src],
deps = [
":scenario_framework",
"//c-toxcore/toxav",
"//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_events",
],
) for src in glob(["scenario_*_test.c"])]

View File

@@ -0,0 +1,99 @@
add_library(scenario_framework
framework/framework.c
framework/framework.h)
target_link_libraries(scenario_framework PUBLIC misc_tools)
if(TARGET toxcore_static)
target_link_libraries(scenario_framework PUBLIC toxcore_static)
else()
target_link_libraries(scenario_framework PUBLIC toxcore_shared)
endif()
if(TARGET pthreads4w::pthreads4w)
target_link_libraries(scenario_framework PUBLIC pthreads4w::pthreads4w)
elseif(TARGET PThreads4W::PThreads4W)
target_link_libraries(scenario_framework PUBLIC PThreads4W::PThreads4W)
elseif(TARGET Threads::Threads)
target_link_libraries(scenario_framework PUBLIC Threads::Threads)
endif()
function(scenario_test target)
add_executable(auto_${target}_test ${target}_test.c)
target_link_libraries(auto_${target}_test PRIVATE misc_tools scenario_framework)
add_test(NAME ${target} COMMAND auto_${target}_test)
set_tests_properties(${target} PROPERTIES TIMEOUT "${TEST_TIMEOUT_SECONDS}")
# add the source dir as environment variable, so the testdata can be found
set_tests_properties(${target} PROPERTIES ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw;srcdir=${CMAKE_CURRENT_SOURCE_DIR}/..")
endfunction()
scenario_test(scenario_avatar)
scenario_test(scenario_bootstrap)
scenario_test(scenario_conference)
scenario_test(scenario_conference_double_invite)
scenario_test(scenario_conference_invite_merge)
scenario_test(scenario_conference_offline)
scenario_test(scenario_conference_peer_nick)
scenario_test(scenario_conference_query)
scenario_test(scenario_conference_simple)
scenario_test(scenario_conference_two)
scenario_test(scenario_dht_nodes_response_api)
scenario_test(scenario_events)
scenario_test(scenario_file_cancel)
scenario_test(scenario_file_seek)
scenario_test(scenario_file_transfer)
scenario_test(scenario_friend_connection)
scenario_test(scenario_friend_delete)
scenario_test(scenario_friend_query)
scenario_test(scenario_friend_read_receipt)
scenario_test(scenario_friend_request)
scenario_test(scenario_friend_request_spam)
scenario_test(scenario_group_general)
scenario_test(scenario_group_invite)
scenario_test(scenario_group_message)
scenario_test(scenario_group_moderation)
scenario_test(scenario_group_save)
scenario_test(scenario_group_state)
scenario_test(scenario_group_sync)
scenario_test(scenario_group_tcp)
scenario_test(scenario_group_topic)
scenario_test(scenario_lan_discovery)
scenario_test(scenario_lossless_packet)
scenario_test(scenario_lossy_packet)
scenario_test(scenario_message)
scenario_test(scenario_netprof)
scenario_test(scenario_nospam)
scenario_test(scenario_overflow_recvq)
scenario_test(scenario_overflow_sendq)
scenario_test(scenario_reconnect)
scenario_test(scenario_save_friend)
scenario_test(scenario_save_load)
scenario_test(scenario_self_query)
scenario_test(scenario_send_message)
scenario_test(scenario_set_name)
scenario_test(scenario_set_status_message)
scenario_test(scenario_tox_many)
scenario_test(scenario_tox_many_tcp)
scenario_test(scenario_typing)
scenario_test(scenario_user_status)
if(BUILD_TOXAV)
scenario_test(scenario_toxav_basic)
scenario_test(scenario_toxav_many)
scenario_test(scenario_conference_av)
if(TARGET libvpx::libvpx)
target_link_libraries(auto_scenario_toxav_basic_test PRIVATE libvpx::libvpx)
target_link_libraries(auto_scenario_toxav_many_test PRIVATE libvpx::libvpx)
elseif(TARGET PkgConfig::VPX)
target_link_libraries(auto_scenario_toxav_basic_test PRIVATE PkgConfig::VPX)
target_link_libraries(auto_scenario_toxav_many_test PRIVATE PkgConfig::VPX)
else()
target_link_libraries(auto_scenario_toxav_basic_test PRIVATE ${VPX_LIBRARIES})
target_link_directories(auto_scenario_toxav_basic_test PRIVATE ${VPX_LIBRARY_DIRS})
target_include_directories(auto_scenario_toxav_basic_test SYSTEM PRIVATE ${VPX_INCLUDE_DIRS})
target_compile_options(auto_scenario_toxav_basic_test PRIVATE ${VPX_CFLAGS_OTHER})
target_link_libraries(auto_scenario_toxav_many_test PRIVATE ${VPX_LIBRARIES})
target_link_directories(auto_scenario_toxav_many_test PRIVATE ${VPX_LIBRARY_DIRS})
target_include_directories(auto_scenario_toxav_many_test SYSTEM PRIVATE ${VPX_INCLUDE_DIRS})
target_compile_options(auto_scenario_toxav_many_test PRIVATE ${VPX_CFLAGS_OTHER})
endif()
endif()

View File

@@ -0,0 +1,79 @@
# Tox Scenario Framework
Tests should read like protocol specifications. Instead of managing manual loops
and shared global state, each "node" in a test is assigned a script (sequence of
tasks) that it executes concurrently with other nodes in a simulated
environment.
### 1. `ToxScenario`
The container for a single test case.
- Manages a collection of `ToxNode` instances.
- Owns the **Virtual Clock**. All nodes in a scenario share the same timeline,
making timeouts and delays deterministic.
- Handles the orchestration of the event loop (`tox_iterate`).
### 2. `ToxNode`
A wrapper around a `Tox` instance and its associated state.
- Each node has its own `Tox_Dispatch`.
- Nodes are identified by a simple index or a string alias (e.g., "Alice",
"Bob").
- Encapsulates the node's progress through its assigned Script.
### 3. `tox_node_script_cb`
A C function that defines what a node does. Because it runs in its own thread,
you can use standard C control flow, local variables, and loops.
## Example Usage
```c
void alice_script(ToxNode *self, void *ctx) {
// Step 1: Wait for DHT connection
WAIT_UNTIL(tox_node_is_self_connected(self));
// Step 2: Send a message to Bob (friend 0)
uint8_t msg[] = "Hello Bob";
tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), NULL);
// Step 3: Wait for a specific response event
WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE);
}
void test_simple_interaction() {
ToxScenario *s = tox_scenario_new(argc, argv, 10000); // 10s virtual timeout
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, NULL);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, NULL);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
ck_assert(res == TOX_SCENARIO_DONE);
tox_scenario_free(s);
}
```
## Execution Model: Cooperative Multi-threading
The scenario framework uses `pthread` to provide a natural programming model
while maintaining a deterministic virtual clock:
1. **Runner**: Orchestrates the scenario in "Ticks" (default 10ms).
2. **Nodes**: Run concurrently in their own threads but **synchronize** at
every tick.
3. **Yielding**: When a script calls `WAIT_UNTIL`, `WAIT_FOR_EVENT`, or
`tox_scenario_yield`, it yields control back to the runner.
4. **Time Advancement**: The runner advances the virtual clock and signals all
nodes to proceed only after all active nodes have yielded.
5. **Barriers**: Nodes can synchronize their progress using
`tox_scenario_barrier_wait(self)`. This function blocks the calling node
until all other active nodes have also reached the barrier. This is useful
for ensuring setup steps (like group creation or connection establishment)
are complete across all nodes before proceeding to the next phase of the
test.

View File

@@ -0,0 +1,800 @@
#include "framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <stdarg.h>
#include "../../../testing/misc_tools.h"
#include "../../../toxcore/tox_struct.h"
#include "../../../toxcore/network.h"
#define MAX_NODES 128
typedef struct {
Tox_Connection connection_status;
bool finished;
bool offline;
uint8_t public_key[TOX_PUBLIC_KEY_SIZE];
uint8_t dht_id[TOX_PUBLIC_KEY_SIZE];
uint8_t address[TOX_ADDRESS_SIZE];
uint16_t udp_port;
} ToxNodeMirror;
struct ToxNode {
Tox *tox;
Tox_Options *options;
Tox_Dispatch *dispatch;
uint32_t index;
char *alias;
ToxScenario *scenario;
pthread_t thread;
tox_node_script_cb *script;
void *script_ctx;
size_t script_ctx_size;
void *mirrored_ctx;
void *mirrored_ctx_public;
ToxNodeMirror mirror;
ToxNodeMirror mirror_public;
bool finished;
bool offline;
uint32_t barrier_index;
uint64_t last_tick;
bool seen_events[256];
};
struct ToxScenario {
ToxNode *nodes[MAX_NODES];
uint32_t num_nodes;
uint32_t num_active;
uint32_t num_ready;
uint64_t virtual_clock;
uint64_t timeout_ms;
uint64_t tick_count;
pthread_mutex_t mutex;
pthread_mutex_t clock_mutex;
pthread_cond_t cond_runner;
pthread_cond_t cond_nodes;
bool run_started;
bool trace_enabled;
bool event_log_enabled;
struct {
const char *name;
uint32_t count;
} barrier;
};
static uint64_t get_scenario_clock(void *user_data)
{
ToxScenario *s = (ToxScenario *)user_data;
pthread_mutex_lock(&s->clock_mutex);
uint64_t time = s->virtual_clock;
pthread_mutex_unlock(&s->clock_mutex);
return time;
}
static void framework_debug_log(Tox *tox, Tox_Log_Level level, const char *file, uint32_t line,
const char *func, const char *message, void *user_data)
{
ToxNode *node = (ToxNode *)user_data;
ck_assert(node != nullptr);
if (level == TOX_LOG_LEVEL_TRACE) {
if (node == nullptr || !node->scenario->trace_enabled) {
return;
}
}
const char *level_name = "UNKNOWN";
switch (level) {
case TOX_LOG_LEVEL_TRACE:
level_name = "TRACE";
break;
case TOX_LOG_LEVEL_DEBUG:
level_name = "DEBUG";
break;
case TOX_LOG_LEVEL_INFO:
level_name = "INFO";
break;
case TOX_LOG_LEVEL_WARNING:
level_name = "WARN";
break;
case TOX_LOG_LEVEL_ERROR:
level_name = "ERROR";
break;
}
const uint64_t relative_time = node ? (get_scenario_clock(node->scenario) - 1000) : 0;
fprintf(stderr, "[%08lu] [%s] %s %s:%u %s: %s\n", (unsigned long)relative_time,
node != nullptr ? node->alias : "Unknown", level_name, file, line, func, message);
}
void tox_node_log(ToxNode *node, const char *format, ...)
{
ck_assert(node != nullptr);
va_list args;
va_start(args, format);
uint64_t relative_time = get_scenario_clock(node->scenario) - 1000;
fprintf(stderr, "[%08lu] [%s] ", (unsigned long)relative_time, node->alias ? node->alias : "Unknown");
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
}
void tox_scenario_log(const ToxScenario *s, const char *format, ...)
{
va_list args;
va_start(args, format);
uint64_t relative_time = get_scenario_clock((void *)(uintptr_t)s) - 1000;
fprintf(stderr, "[%08lu] [Runner] ", (unsigned long)relative_time);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
}
ToxScenario *tox_scenario_new(int argc, char *const argv[], uint64_t timeout_ms)
{
static bool seeded = false;
if (!seeded) {
srand(time(nullptr));
seeded = true;
}
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--wait-time") == 0 && i + 1 < argc) {
timeout_ms = (uint64_t)atoll(argv[i + 1]) * 1000;
break;
}
}
ToxScenario *s = (ToxScenario *)calloc(1, sizeof(ToxScenario));
ck_assert(s != nullptr);
s->timeout_ms = timeout_ms;
s->virtual_clock = 1000;
s->trace_enabled = (getenv("TOX_TRACE") != nullptr);
s->event_log_enabled = (getenv("TOX_EVENT_LOG") != nullptr);
pthread_mutex_init(&s->mutex, nullptr);
pthread_mutex_init(&s->clock_mutex, nullptr);
pthread_cond_init(&s->cond_runner, nullptr);
pthread_cond_init(&s->cond_nodes, nullptr);
return s;
}
void tox_scenario_free(ToxScenario *s)
{
for (uint32_t i = 0; i < s->num_nodes; ++i) {
ToxNode *node = s->nodes[i];
ck_assert(node != nullptr);
tox_dispatch_free(node->dispatch);
tox_kill(node->tox);
tox_options_free(node->options);
free(node->alias);
free(node->mirrored_ctx);
free(node->mirrored_ctx_public);
free(node);
}
pthread_mutex_destroy(&s->mutex);
pthread_mutex_destroy(&s->clock_mutex);
pthread_cond_destroy(&s->cond_runner);
pthread_cond_destroy(&s->cond_nodes);
free(s);
}
Tox *tox_node_get_tox(const ToxNode *node)
{
ck_assert(node != nullptr);
ToxScenario *s = node->scenario;
pthread_mutex_lock(&s->mutex);
Tox *tox = node->tox;
pthread_mutex_unlock(&s->mutex);
return tox;
}
void tox_node_get_address(const ToxNode *node, uint8_t *address)
{
ck_assert(node != nullptr);
ck_assert(address != nullptr);
memcpy(address, node->mirror_public.address, TOX_ADDRESS_SIZE);
}
ToxScenario *tox_node_get_scenario(const ToxNode *node)
{
ck_assert(node != nullptr);
return node->scenario;
}
Tox_Connection tox_node_get_connection_status(const ToxNode *node)
{
ck_assert(node != nullptr);
return node->mirror_public.connection_status;
}
Tox_Connection tox_node_get_friend_connection_status(const ToxNode *node, uint32_t friend_number)
{
ck_assert(node != nullptr);
// Note: Friend status is not currently mirrored.
// This is safe if called by the node on itself.
// If called on a peer, it might still race.
Tox_Err_Friend_Query err;
Tox_Connection conn = tox_friend_get_connection_status(node->tox, friend_number, &err);
if (err != TOX_ERR_FRIEND_QUERY_OK) {
return TOX_CONNECTION_NONE;
}
return conn;
}
bool tox_node_is_self_connected(const ToxNode *node)
{
return tox_node_get_connection_status(node) != TOX_CONNECTION_NONE;
}
bool tox_node_is_friend_connected(const ToxNode *node, uint32_t friend_number)
{
return tox_node_get_friend_connection_status(node, friend_number) != TOX_CONNECTION_NONE;
}
uint32_t tox_node_get_conference_peer_count(const ToxNode *node, uint32_t conference_number)
{
ck_assert(node != nullptr);
Tox_Err_Conference_Peer_Query err;
uint32_t count = tox_conference_peer_count(node->tox, conference_number, &err);
if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
return 0;
}
return count;
}
void tox_node_set_offline(ToxNode *node, bool offline)
{
ck_assert(node != nullptr);
node->offline = offline;
}
bool tox_node_is_offline(const ToxNode *node)
{
ck_assert(node != nullptr);
return node->mirror_public.offline;
}
bool tox_node_is_finished(const ToxNode *node)
{
ck_assert(node != nullptr);
ToxScenario *s = node->scenario;
pthread_mutex_lock(&s->mutex);
bool finished = node->mirror_public.finished;
pthread_mutex_unlock(&s->mutex);
return finished;
}
bool tox_scenario_is_running(ToxNode *self)
{
bool running;
pthread_mutex_lock(&self->scenario->mutex);
running = self->scenario->run_started;
pthread_mutex_unlock(&self->scenario->mutex);
return running;
}
bool tox_node_friend_name_is(const ToxNode *node, uint32_t friend_number, const uint8_t *name, size_t length)
{
ck_assert(node != nullptr);
Tox_Err_Friend_Query err;
size_t actual_len = tox_friend_get_name_size(node->tox, friend_number, &err);
if (err != TOX_ERR_FRIEND_QUERY_OK || actual_len != length) {
return false;
}
uint8_t *actual_name = (uint8_t *)malloc(length);
ck_assert(actual_name != nullptr);
tox_friend_get_name(node->tox, friend_number, actual_name, nullptr);
bool match = (memcmp(actual_name, name, length) == 0);
free(actual_name);
return match;
}
bool tox_node_friend_status_message_is(const ToxNode *node, uint32_t friend_number, const uint8_t *msg, size_t length)
{
ck_assert(node != nullptr);
Tox_Err_Friend_Query err;
size_t actual_len = tox_friend_get_status_message_size(node->tox, friend_number, &err);
if (err != TOX_ERR_FRIEND_QUERY_OK || actual_len != length) {
return false;
}
uint8_t *actual_msg = (uint8_t *)malloc(length);
ck_assert(actual_msg != nullptr);
tox_friend_get_status_message(node->tox, friend_number, actual_msg, nullptr);
bool match = (memcmp(actual_msg, msg, length) == 0);
free(actual_msg);
return match;
}
bool tox_node_friend_typing_is(const ToxNode *node, uint32_t friend_number, bool is_typing)
{
ck_assert(node != nullptr);
Tox_Err_Friend_Query err;
bool actual = tox_friend_get_typing(node->tox, friend_number, &err);
return (err == TOX_ERR_FRIEND_QUERY_OK && actual == is_typing);
}
void tox_scenario_yield(ToxNode *self)
{
// 1. Poll Tox (Strictly in the node's thread), only if not offline
if (!tox_node_is_offline(self)) {
Tox_Err_Events_Iterate ev_err;
Tox_Events *events = tox_events_iterate(self->tox, false, &ev_err);
if (events != nullptr) {
uint32_t size = tox_events_get_size(events);
for (uint32_t j = 0; j < size; ++j) {
const Tox_Event *ev = tox_events_get(events, j);
const Tox_Event_Type type = tox_event_get_type(ev);
self->seen_events[type] = true;
if (self->scenario->event_log_enabled) {
tox_node_log(self, "Received event: %s (%u)", tox_event_type_to_string(type), type);
}
}
tox_dispatch_invoke(self->dispatch, events, self);
tox_events_free(events);
}
}
ToxScenario *s = self->scenario;
// 2. Update mirror (Outside lock to reduce contention)
self->mirror.connection_status = tox_self_get_connection_status(self->tox);
self->mirror.offline = self->offline;
tox_self_get_public_key(self->tox, self->mirror.public_key);
tox_self_get_dht_id(self->tox, self->mirror.dht_id);
tox_self_get_address(self->tox, self->mirror.address);
Tox_Err_Get_Port port_err;
self->mirror.udp_port = tox_self_get_udp_port(self->tox, &port_err);
if (self->mirrored_ctx != nullptr && self->script_ctx != nullptr) {
memcpy(self->mirrored_ctx, self->script_ctx, self->script_ctx_size);
}
pthread_mutex_lock(&s->mutex);
self->mirror.finished = self->finished;
self->last_tick = s->tick_count;
s->num_ready++;
// Wake runner
pthread_cond_signal(&s->cond_runner);
// Wait for next tick
while (self->last_tick == s->tick_count && s->run_started) {
pthread_cond_wait(&s->cond_nodes, &s->mutex);
}
pthread_mutex_unlock(&s->mutex);
// Give the OS a chance to deliver UDP packets
c_sleep(1);
}
void tox_scenario_wait_for_event(ToxNode *self, Tox_Event_Type type)
{
while (!self->seen_events[type] && tox_scenario_is_running(self)) {
tox_scenario_yield(self);
}
}
void tox_scenario_barrier_wait(ToxNode *self)
{
ToxScenario *s = self->scenario;
pthread_mutex_lock(&s->mutex);
uint32_t my_barrier_index = ++self->barrier_index;
pthread_mutex_unlock(&s->mutex);
while (tox_scenario_is_running(self)) {
// Even if all nodes have reached the barrier, we MUST yield at least once
// to ensure the runner has a chance to synchronize the snapshots for this barrier.
tox_scenario_yield(self);
pthread_mutex_lock(&s->mutex);
uint32_t reached = 0;
for (uint32_t i = 0; i < s->num_nodes; ++i) {
if (s->nodes[i]->barrier_index >= my_barrier_index || s->nodes[i]->finished) {
reached++;
}
}
const bool done = reached >= s->num_nodes;
pthread_mutex_unlock(&s->mutex);
if (done) {
break;
}
}
}
void tox_node_wait_for_self_connected(ToxNode *self)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
}
void tox_node_wait_for_friend_connected(ToxNode *self, uint32_t friend_number)
{
WAIT_UNTIL(tox_node_is_friend_connected(self, friend_number));
}
static void *node_thread_wrapper(void *arg)
{
ToxNode *node = (ToxNode *)arg;
ck_assert(node != nullptr);
ToxScenario *s = node->scenario;
// Wait for the starting signal from runner
pthread_mutex_lock(&s->mutex);
s->num_ready++;
pthread_cond_signal(&s->cond_runner);
while (s->tick_count == 0 && s->run_started) {
pthread_cond_wait(&s->cond_nodes, &s->mutex);
}
pthread_mutex_unlock(&s->mutex);
// Run the actual script
srand(time(nullptr) + node->index);
node->script(node, node->script_ctx);
// After the script is done, keep polling until the scenario is over
pthread_mutex_lock(&s->mutex);
node->finished = true;
s->num_active--;
uint32_t active = s->num_active;
pthread_mutex_unlock(&s->mutex);
tox_node_log(node, "finished script, active nodes remaining: %u", active);
pthread_mutex_lock(&s->mutex);
pthread_cond_signal(&s->cond_runner);
while (s->run_started) {
uint64_t tick_at_start = s->tick_count;
s->num_ready++;
pthread_cond_signal(&s->cond_runner);
while (tick_at_start == s->tick_count && s->run_started) {
pthread_cond_wait(&s->cond_nodes, &s->mutex);
}
pthread_mutex_unlock(&s->mutex);
// 2. Update mirror outside lock
node->mirror.connection_status = tox_self_get_connection_status(node->tox);
node->mirror.finished = node->finished;
node->mirror.offline = node->offline;
if (node->mirrored_ctx != nullptr && node->script_ctx != nullptr) {
memcpy(node->mirrored_ctx, node->script_ctx, node->script_ctx_size);
}
// Poll Tox even when script is "finished"
Tox_Err_Events_Iterate ev_err;
Tox_Events *events = tox_events_iterate(node->tox, false, &ev_err);
if (events != nullptr) {
tox_dispatch_invoke(node->dispatch, events, node);
tox_events_free(events);
}
pthread_mutex_lock(&s->mutex);
}
pthread_mutex_unlock(&s->mutex);
return nullptr;
}
static Tox *create_tox_with_port_retry(Tox_Options *opts, void *log_user_data, Tox_Err_New *out_err)
{
tox_options_set_log_user_data(opts, log_user_data);
if (tox_options_get_start_port(opts) == 0 && tox_options_get_end_port(opts) == 0) {
tox_options_set_start_port(opts, TOX_PORT_DEFAULT);
tox_options_set_end_port(opts, 65535);
}
uint16_t tcp_port = tox_options_get_tcp_port(opts);
Tox *tox = nullptr;
for (int i = 0; i < 50; ++i) {
tox = tox_new(opts, out_err);
if (*out_err != TOX_ERR_NEW_PORT_ALLOC || tcp_port == 0) {
break;
}
tcp_port++;
tox_options_set_tcp_port(opts, tcp_port);
}
return tox;
}
ToxNode *tox_scenario_add_node_ex(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size, const Tox_Options *options)
{
if (s->num_nodes >= MAX_NODES) {
return nullptr;
}
ToxNode *node = (ToxNode *)calloc(1, sizeof(ToxNode));
ck_assert(node != nullptr);
if (alias != nullptr) {
size_t len = strlen(alias) + 1;
node->alias = (char *)malloc(len);
ck_assert(node->alias != nullptr);
memcpy(node->alias, alias, len);
}
node->index = s->num_nodes;
node->scenario = s;
node->script = script;
node->script_ctx = ctx;
node->script_ctx_size = ctx_size;
if (ctx_size > 0) {
node->mirrored_ctx = calloc(1, ctx_size);
ck_assert(node->mirrored_ctx != nullptr);
node->mirrored_ctx_public = calloc(1, ctx_size);
ck_assert(node->mirrored_ctx_public != nullptr);
if (ctx != nullptr) {
memcpy(node->mirrored_ctx, ctx, ctx_size);
memcpy(node->mirrored_ctx_public, ctx, ctx_size);
}
}
Tox_Options *opts = tox_options_new(nullptr);
if (options != nullptr) {
tox_options_copy(opts, options);
} else {
tox_options_set_ipv6_enabled(opts, false);
tox_options_set_local_discovery_enabled(opts, false);
}
tox_options_set_log_callback(opts, framework_debug_log);
Tox_Err_New err;
node->tox = create_tox_with_port_retry(opts, node, &err);
if (err == TOX_ERR_NEW_OK) {
ck_assert(node->tox != nullptr);
node->options = tox_options_new(nullptr);
tox_options_copy(node->options, opts);
}
tox_options_free(opts);
if (err != TOX_ERR_NEW_OK) {
tox_scenario_log(s, "Failed to create Tox instance for node %s: %u", alias, err);
free(node);
return nullptr;
}
node->dispatch = tox_dispatch_new(nullptr);
tox_events_init(node->tox);
mono_time_set_current_time_callback(node->tox->mono_time, get_scenario_clock, s);
// Initial mirror population
node->mirror.connection_status = tox_self_get_connection_status(node->tox);
tox_self_get_public_key(node->tox, node->mirror.public_key);
tox_self_get_dht_id(node->tox, node->mirror.dht_id);
tox_self_get_address(node->tox, node->mirror.address);
Tox_Err_Get_Port port_err;
node->mirror.udp_port = tox_self_get_udp_port(node->tox, &port_err);
node->mirror_public = node->mirror;
s->nodes[s->num_nodes++] = node;
return node;
}
ToxNode *tox_scenario_add_node(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size)
{
return tox_scenario_add_node_ex(s, alias, script, ctx, ctx_size, nullptr);
}
ToxNode *tox_scenario_get_node(ToxScenario *s, uint32_t index)
{
if (index >= s->num_nodes) {
return nullptr;
}
return s->nodes[index];
}
const char *tox_node_get_alias(const ToxNode *node)
{
ck_assert(node != nullptr);
return node->alias;
}
uint32_t tox_node_get_index(const ToxNode *node)
{
ck_assert(node != nullptr);
return node->index;
}
void *tox_node_get_script_ctx(const ToxNode *node)
{
ck_assert(node != nullptr);
return node->script_ctx;
}
const void *tox_node_get_peer_ctx(const ToxNode *node)
{
ck_assert(node != nullptr);
return node->mirrored_ctx_public;
}
Tox_Dispatch *tox_node_get_dispatch(const ToxNode *node)
{
ck_assert(node != nullptr);
return node->dispatch;
}
uint64_t tox_scenario_get_time(ToxScenario *s)
{
pthread_mutex_lock(&s->clock_mutex);
uint64_t time = s->virtual_clock;
pthread_mutex_unlock(&s->clock_mutex);
return time;
}
ToxScenarioStatus tox_scenario_run(ToxScenario *s)
{
pthread_mutex_lock(&s->mutex);
s->num_active = s->num_nodes;
s->num_ready = 0;
s->tick_count = 0;
s->run_started = true;
pthread_mutex_unlock(&s->mutex);
// Start all node threads
for (uint32_t i = 0; i < s->num_nodes; ++i) {
pthread_create(&s->nodes[i]->thread, nullptr, node_thread_wrapper, s->nodes[i]);
}
pthread_mutex_lock(&s->mutex);
uint64_t start_clock = s->virtual_clock;
uint64_t deadline = start_clock + s->timeout_ms;
while (s->num_active > 0 && s->virtual_clock < deadline) {
// 1. Wait until all nodes (including finished ones) have reached the barrier
while (s->num_ready < s->num_nodes) {
pthread_cond_wait(&s->cond_runner, &s->mutex);
}
// 2. Synchronize Snapshots
for (uint32_t i = 0; i < s->num_nodes; ++i) {
ToxNode *node = s->nodes[i];
ck_assert(node != nullptr);
node->mirror_public = node->mirror;
if (node->mirrored_ctx_public && node->mirrored_ctx) {
memcpy(node->mirrored_ctx_public, node->mirrored_ctx, node->script_ctx_size);
}
}
// 3. Advance tick and clock
s->num_ready = 0;
s->tick_count++;
pthread_mutex_lock(&s->clock_mutex);
s->virtual_clock += TOX_SCENARIO_TICK_MS;
pthread_mutex_unlock(&s->clock_mutex);
if (s->tick_count % 100 == 0) {
uint64_t tick = s->tick_count;
uint32_t active = s->num_active;
pthread_mutex_unlock(&s->mutex);
tox_scenario_log(s, "Tick %lu, active nodes: %u", (unsigned long)tick, active);
pthread_mutex_lock(&s->mutex);
}
// 4. Release nodes for the new tick
pthread_cond_broadcast(&s->cond_nodes);
}
ToxScenarioStatus result = (s->num_active == 0) ? TOX_SCENARIO_DONE : TOX_SCENARIO_TIMEOUT;
if (result == TOX_SCENARIO_TIMEOUT) {
tox_scenario_log(s, "Scenario TIMEOUT after %lu ms (virtual time)", (unsigned long)(s->virtual_clock - start_clock));
}
// Stop nodes
s->run_started = false;
pthread_cond_broadcast(&s->cond_nodes);
pthread_mutex_unlock(&s->mutex);
for (uint32_t i = 0; i < s->num_nodes; ++i) {
pthread_join(s->nodes[i]->thread, nullptr);
}
return result;
}
void tox_node_bootstrap(ToxNode *a, ToxNode *b)
{
uint8_t pk[TOX_PUBLIC_KEY_SIZE];
memcpy(pk, b->mirror_public.dht_id, TOX_PUBLIC_KEY_SIZE);
const uint16_t port = b->mirror_public.udp_port;
Tox_Err_Bootstrap err;
tox_bootstrap(a->tox, "127.0.0.1", port, pk, &err);
if (err != TOX_ERR_BOOTSTRAP_OK) {
tox_node_log(a, "Error bootstrapping from %s: %u", b->alias, err);
}
}
void tox_node_friend_add(ToxNode *a, ToxNode *b)
{
uint8_t pk[TOX_PUBLIC_KEY_SIZE];
memcpy(pk, b->mirror_public.public_key, TOX_PUBLIC_KEY_SIZE);
Tox_Err_Friend_Add err;
tox_friend_add_norequest(a->tox, pk, &err);
if (err != TOX_ERR_FRIEND_ADD_OK) {
tox_node_log(a, "Error adding friend %s: %u", b->alias, err);
}
}
void tox_node_reload(ToxNode *node)
{
ck_assert(node != nullptr);
ToxScenario *s = node->scenario;
pthread_mutex_lock(&s->mutex);
// 1. Save data
size_t size = tox_get_savedata_size(node->tox);
uint8_t *data = (uint8_t *)malloc(size);
ck_assert(data != nullptr);
tox_get_savedata(node->tox, data);
Tox *old_tox = node->tox;
Tox_Dispatch *old_dispatch = node->dispatch;
// Invalidate node state while reloading
node->tox = nullptr;
node->dispatch = nullptr;
pthread_mutex_unlock(&s->mutex);
// 2. Kill old instance
tox_dispatch_free(old_dispatch);
tox_kill(old_tox);
// 3. Create new instance from save
Tox_Options *opts = tox_options_new(nullptr);
ck_assert(opts != nullptr);
tox_options_copy(opts, node->options);
tox_options_set_savedata_type(opts, TOX_SAVEDATA_TYPE_TOX_SAVE);
tox_options_set_savedata_data(opts, data, size);
Tox_Err_New err;
Tox *new_tox = create_tox_with_port_retry(opts, node, &err);
tox_options_free(opts);
free(data);
if (err != TOX_ERR_NEW_OK) {
tox_node_log(node, "Failed to reload Tox instance: %u", err);
return;
}
ck_assert_msg(new_tox != nullptr, "tox_new said OK but returned NULL");
Tox_Dispatch *new_dispatch = tox_dispatch_new(nullptr);
tox_events_init(new_tox);
mono_time_set_current_time_callback(new_tox->mono_time, get_scenario_clock, s);
pthread_mutex_lock(&s->mutex);
node->tox = new_tox;
node->dispatch = new_dispatch;
pthread_mutex_unlock(&s->mutex);
// Re-bootstrap from siblings
for (uint32_t i = 0; i < s->num_nodes; ++i) {
if (i != node->index) {
tox_node_bootstrap(node, s->nodes[i]);
}
}
}

View File

@@ -0,0 +1,215 @@
#ifndef TOX_TEST_FRAMEWORK_H
#define TOX_TEST_FRAMEWORK_H
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "../../../toxcore/attributes.h"
#include "../../../toxcore/ccompat.h"
#include "../../../toxcore/tox.h"
#include "../../../toxcore/tox_dispatch.h"
// --- Constants ---
#define TOX_SCENARIO_TICK_MS 50
// --- Opaque Types ---
typedef struct ToxNode ToxNode;
typedef struct ToxScenario ToxScenario;
typedef enum {
TOX_SCENARIO_OK,
TOX_SCENARIO_RUNNING,
TOX_SCENARIO_DONE,
TOX_SCENARIO_ERROR,
TOX_SCENARIO_TIMEOUT
} ToxScenarioStatus;
/**
* A script function that defines the behavior of a node.
*/
typedef void tox_node_script_cb(ToxNode *self, void *ctx);
// --- Core API ---
ToxScenario *tox_scenario_new(int argc, char *const argv[], uint64_t timeout_ms);
void tox_scenario_free(ToxScenario *s);
/**
* Adds a node to the scenario and assigns it a script.
* @param ctx_size The size of the context struct to be mirrored (snapshot) at each tick.
*/
ToxNode *tox_scenario_add_node(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size);
/**
* Extended version of add_node that accepts custom Tox_Options.
*/
ToxNode *tox_scenario_add_node_ex(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size, const Tox_Options *options);
/**
* Returns a node by its index in the scenario.
*/
ToxNode *tox_scenario_get_node(ToxScenario *s, uint32_t index);
/**
* Runs the scenario until all nodes complete their scripts or timeout occurs.
*/
ToxScenarioStatus tox_scenario_run(ToxScenario *s);
// --- Logging API ---
/**
* Logs a message with the node's alias/index and current virtual time.
*/
void tox_node_log(ToxNode *node, const char *format, ...) GNU_PRINTF(2, 3);
/**
* Logs a message from the scenario runner with current virtual time.
*/
void tox_scenario_log(const ToxScenario *s, const char *format, ...) GNU_PRINTF(2, 3);
// --- Script API (Callable from within scripts) ---
/**
* Returns the underlying Tox instance.
*/
Tox *tox_node_get_tox(const ToxNode *node);
/**
* Returns the mirrored address of the node.
*/
void tox_node_get_address(const ToxNode *node, uint8_t *address);
/**
* Returns the scenario this node belongs to.
*/
ToxScenario *tox_node_get_scenario(const ToxNode *node);
/**
* Returns the alias of the node.
*/
const char *tox_node_get_alias(const ToxNode *node);
/**
* Returns the index of the node in the scenario.
*/
uint32_t tox_node_get_index(const ToxNode *node);
/**
* Returns the script context associated with this node.
*/
void *tox_node_get_script_ctx(const ToxNode *node);
/**
* Returns a read-only snapshot of a peer's context.
* The snapshot is updated at every tick.
*/
const void *tox_node_get_peer_ctx(const ToxNode *node);
/**
* Returns the event dispatcher associated with this node.
*/
Tox_Dispatch *tox_node_get_dispatch(const ToxNode *node);
/**
* Predicate helpers for common wait conditions.
*/
bool tox_node_is_self_connected(const ToxNode *node);
bool tox_node_is_friend_connected(const ToxNode *node, uint32_t friend_number);
Tox_Connection tox_node_get_connection_status(const ToxNode *node);
Tox_Connection tox_node_get_friend_connection_status(const ToxNode *node, uint32_t friend_number);
/**
* Conference helpers.
*/
uint32_t tox_node_get_conference_peer_count(const ToxNode *node, uint32_t conference_number);
/**
* Network simulation.
*/
void tox_node_set_offline(ToxNode *node, bool offline);
bool tox_node_is_offline(const ToxNode *node);
/**
* Returns true if the node has finished its script.
*/
bool tox_node_is_finished(const ToxNode *node);
/**
* High-level predicates for specific state changes.
*/
bool tox_node_friend_name_is(const ToxNode *node, uint32_t friend_number, const uint8_t *name, size_t length);
bool tox_node_friend_status_message_is(const ToxNode *node, uint32_t friend_number, const uint8_t *msg, size_t length);
bool tox_node_friend_typing_is(const ToxNode *node, uint32_t friend_number, bool is_typing);
/**
* Yields execution to the next tick.
*/
void tox_scenario_yield(ToxNode *self);
/**
* Returns the current virtual time in milliseconds.
*/
uint64_t tox_scenario_get_time(ToxScenario *s);
/**
* Returns true if the scenario is still running.
*/
bool tox_scenario_is_running(ToxNode *self);
/**
* Blocks until a condition is true. Polls every tick.
*/
#define WAIT_UNTIL(cond) do { while(!(cond) && tox_scenario_is_running(self)) { tox_scenario_yield(self); } } while(0)
/**
* Blocks until a specific event is observed.
*/
void tox_scenario_wait_for_event(ToxNode *self, Tox_Event_Type type);
#define WAIT_FOR_EVENT(type) tox_scenario_wait_for_event(self, type)
/**
* Synchronization barriers.
* Blocks until all active nodes have reached this barrier.
*/
void tox_scenario_barrier_wait(ToxNode *self);
/**
* High-level wait helpers.
*/
void tox_node_wait_for_self_connected(ToxNode *self);
void tox_node_wait_for_friend_connected(ToxNode *self, uint32_t friend_number);
// --- High-Level Helpers ---
void tox_node_bootstrap(ToxNode *a, ToxNode *b);
void tox_node_friend_add(ToxNode *a, ToxNode *b);
/**
* Reloads a node from its own savedata.
* This simulates a client restart.
*/
void tox_node_reload(ToxNode *node);
#ifndef ck_assert
#define ck_assert(ok) do { \
if (!(ok)) { \
fprintf(stderr, "%s:%d: failed `%s'\n", __FILE__, __LINE__, #ok); \
exit(7); \
} \
} while (0)
#define ck_assert_msg(ok, ...) do { \
if (!(ok)) { \
fprintf(stderr, "%s:%d: failed `%s': ", __FILE__, __LINE__, #ok); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
exit(7); \
} \
} while (0)
#endif
#endif // TOX_TEST_FRAMEWORK_H

View File

@@ -0,0 +1,144 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define AVATAR_SIZE 100
static const uint8_t avatar_data[AVATAR_SIZE] = "This is a fake avatar image data. It should be longer but for testing this is enough.";
typedef struct {
uint8_t avatar_hash[TOX_HASH_LENGTH];
bool transfer_finished;
} AvatarState;
static void on_file_chunk_request(const Tox_Event_File_Chunk_Request *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
AvatarState *state = (AvatarState *)tox_node_get_script_ctx(self);
uint32_t friend_number = tox_event_file_chunk_request_get_friend_number(event);
uint32_t file_number = tox_event_file_chunk_request_get_file_number(event);
uint64_t position = tox_event_file_chunk_request_get_position(event);
size_t length = tox_event_file_chunk_request_get_length(event);
if (length == 0) {
state->transfer_finished = true;
return;
}
if (position >= AVATAR_SIZE) {
tox_file_send_chunk(tox_node_get_tox(self), friend_number, file_number, position, nullptr, 0, nullptr);
state->transfer_finished = true;
return;
}
size_t to_send = AVATAR_SIZE - (size_t)position;
if (to_send > length) {
to_send = length;
}
tox_file_send_chunk(tox_node_get_tox(self), friend_number, file_number, position, avatar_data + position, to_send, nullptr);
}
static void alice_script(ToxNode *self, void *ctx)
{
AvatarState *state = (AvatarState *)ctx;
tox_events_callback_file_chunk_request(tox_node_get_dispatch(self), on_file_chunk_request);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
Tox *tox = tox_node_get_tox(self);
tox_hash(state->avatar_hash, avatar_data, AVATAR_SIZE);
tox_node_log(self, "Sending avatar request to Bob...");
Tox_Err_File_Send err_send;
tox_file_send(tox, 0, TOX_FILE_KIND_AVATAR, AVATAR_SIZE, state->avatar_hash, (const uint8_t *)"avatar.png", 10, &err_send);
ck_assert(err_send == TOX_ERR_FILE_SEND_OK);
WAIT_UNTIL(state->transfer_finished);
tox_node_log(self, "Avatar transfer finished from Alice side.");
}
typedef struct {
bool avatar_received;
uint8_t received_data[AVATAR_SIZE];
size_t received_len;
} BobState;
static void on_file_recv(const Tox_Event_File_Recv *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
uint32_t kind = tox_event_file_recv_get_kind(event);
uint32_t friend_number = tox_event_file_recv_get_friend_number(event);
uint32_t file_number = tox_event_file_recv_get_file_number(event);
if (kind == TOX_FILE_KIND_AVATAR) {
tox_node_log(self, "Receiving avatar from Alice, resuming...");
tox_file_control(tox_node_get_tox(self), friend_number, file_number, TOX_FILE_CONTROL_RESUME, nullptr);
}
}
static void on_file_recv_chunk(const Tox_Event_File_Recv_Chunk *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
BobState *state = (BobState *)tox_node_get_script_ctx(self);
size_t length = tox_event_file_recv_chunk_get_data_length(event);
uint64_t position = tox_event_file_recv_chunk_get_position(event);
if (length == 0) {
state->avatar_received = true;
return;
}
if (position + length <= AVATAR_SIZE) {
memcpy(state->received_data + position, tox_event_file_recv_chunk_get_data(event), length);
state->received_len += length;
}
}
static void bob_script(ToxNode *self, void *ctx)
{
const BobState *state = (const BobState *)ctx;
tox_events_callback_file_recv(tox_node_get_dispatch(self), on_file_recv);
tox_events_callback_file_recv_chunk(tox_node_get_dispatch(self), on_file_recv_chunk);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
WAIT_UNTIL(state->avatar_received);
tox_node_log(self, "Avatar received. Verifying data...");
ck_assert(state->received_len == AVATAR_SIZE);
ck_assert(memcmp(state->received_data, avatar_data, AVATAR_SIZE) == 0);
tox_node_log(self, "Avatar verification successful!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
AvatarState alice_state = {{0}, false};
BobState bob_state = {false, {0}, 0};
tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AvatarState));
tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState));
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,38 @@
#include "framework/framework.h"
#include <stdio.h>
static void alice_script(ToxNode *self, void *ctx)
{
ToxScenario *s = tox_node_get_scenario(self);
ToxNode *bob = tox_scenario_get_node(s, 1);
// Alice just waits for Bob to finish his bootstrap test
WAIT_UNTIL(tox_node_is_finished(bob));
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_log(self, "Waiting for DHT connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Connected to DHT!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 30000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res == TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Scenario completed successfully!");
} else {
tox_scenario_log(s, "Scenario failed with status: %u", res);
}
tox_scenario_free(s);
return (res == TOX_SCENARIO_DONE) ? 0 : 1;
}

View File

@@ -0,0 +1,132 @@
#include "framework/framework.h"
#include "../../toxav/toxav.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_NODES 4
typedef struct {
bool invited_next;
uint32_t audio_received_mask;
uint32_t group_number;
bool joined;
} State;
static void audio_callback(void *tox, uint32_t group_number, uint32_t peer_number, const int16_t *pcm,
unsigned int samples, uint8_t channels, uint32_t sample_rate, void *user_data)
{
(void)tox;
(void)group_number;
(void)sample_rate;
(void)channels;
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
if (samples > 0) {
state->audio_received_mask |= (1 << peer_number);
}
}
static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
uint32_t friend_number = tox_event_conference_invite_get_friend_number(event);
const uint8_t *cookie = tox_event_conference_invite_get_cookie(event);
size_t length = tox_event_conference_invite_get_cookie_length(event);
tox_node_log(self, "Received conference invite from friend %u", friend_number);
state->group_number = toxav_join_av_groupchat(tox_node_get_tox(self), friend_number, cookie, length, audio_callback, self);
ck_assert(state->group_number != (uint32_t) -1);
state->joined = true;
}
static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
uint32_t group_number = tox_event_conference_connected_get_conference_number(event);
if (state->invited_next) {
return;
}
// In a linear graph, friend 0 is i-1, friend 1 is i+1 (if exists)
// We want to invite the next peer.
uint32_t friend_count = tox_self_get_friend_list_size(tox_node_get_tox(self));
if (friend_count > 1 || tox_node_get_index(self) == 0) {
uint32_t friend_to_invite = (tox_node_get_index(self) == 0) ? 0 : 1;
if (friend_to_invite < friend_count) {
Tox_Err_Conference_Invite err;
tox_conference_invite(tox_node_get_tox(self), friend_to_invite, group_number, &err);
if (err == TOX_ERR_CONFERENCE_INVITE_OK) {
tox_node_log(self, "Invited next friend (%u)", friend_to_invite);
state->invited_next = true;
}
}
}
}
static void peer_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_conference_invite(dispatch, on_conference_invite);
tox_events_callback_conference_connected(dispatch, on_conference_connected);
WAIT_UNTIL(tox_node_is_self_connected(self));
if (tox_node_get_index(self) == 0) {
tox_node_log(self, "Creating AV group...");
state->group_number = toxav_add_av_groupchat(tox, audio_callback, self);
ck_assert(state->group_number != (uint32_t) -1);
state->joined = true;
// Peer 0 has only one friend (Peer 1)
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_conference_invite(tox, 0, state->group_number, nullptr);
state->invited_next = true;
}
// Wait until joined and everyone is in the group
WAIT_UNTIL(state->joined);
while (tox_node_get_conference_peer_count(self, state->group_number) < (uint32_t)NUM_NODES) {
tox_scenario_yield(self);
}
tox_node_log(self, "All %u peers in group!", (uint32_t)NUM_NODES);
// Send audio for a bit
const int16_t pcm[960] = {0};
for (int i = 0; i < 40; i++) {
toxav_group_send_audio(tox, state->group_number, pcm, 960, 1, 48000);
tox_scenario_yield(self);
}
tox_node_log(self, "Audio sent. Received audio mask: %u", state->audio_received_mask);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
State states[NUM_NODES] = {0};
ToxNode *nodes[NUM_NODES];
for (uint32_t i = 0; i < NUM_NODES; i++) {
char name[32];
snprintf(name, sizeof(name), "Peer-%u", i);
nodes[i] = tox_scenario_add_node(s, name, peer_script, &states[i], sizeof(State));
}
// Linear graph
for (uint32_t i = 0; i < NUM_NODES - 1; i++) {
tox_node_bootstrap(nodes[i + 1], nodes[i]);
tox_node_friend_add(nodes[i], nodes[i + 1]);
tox_node_friend_add(nodes[i + 1], nodes[i]);
}
ToxScenarioStatus res = tox_scenario_run(s);
tox_scenario_free(s);
return (res == TOX_SCENARIO_DONE) ? 0 : 1;
}

View File

@@ -0,0 +1,97 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
typedef struct {
bool joined;
uint32_t conference;
} State;
static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
if (state->joined) {
tox_node_log(self, "ERROR! Received second invite for already joined conference");
return;
}
Tox_Err_Conference_Join err;
state->conference = tox_conference_join(tox_node_get_tox(self),
tox_event_conference_invite_get_friend_number(event),
tox_event_conference_invite_get_cookie(event),
tox_event_conference_invite_get_cookie_length(event),
&err);
if (err == TOX_ERR_CONFERENCE_JOIN_OK) {
state->joined = true;
tox_node_log(self, "Joined conference %u", state->conference);
}
}
static void alice_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
State *state = (State *)ctx;
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Creating conference...");
Tox_Err_Conference_New err_new;
state->conference = tox_conference_new(tox, &err_new);
state->joined = true;
tox_node_log(self, "Inviting Bob (1st time)...");
tox_conference_invite(tox, 0, state->conference, nullptr);
// Wait for Bob to join
const ToxNode *bob = tox_scenario_get_node(tox_node_get_scenario(self), 1);
const State *bob_view = (const State *)tox_node_get_peer_ctx(bob);
WAIT_UNTIL(bob_view->joined);
tox_node_log(self, "Inviting Bob (2nd time); should be ignored by Bob's script logic...");
tox_conference_invite(tox, 0, state->conference, nullptr);
// Wait some ticks to see if Bob's script logs an error
for (int i = 0; i < 20; ++i) {
tox_scenario_yield(self);
}
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite);
WAIT_UNTIL(tox_node_is_self_connected(self));
const State *state = (const State *)ctx;
WAIT_UNTIL(state->joined);
// Stay alive for Alice's second invite
for (int i = 0; i < 50; ++i) {
tox_scenario_yield(self);
}
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
State s1 = {0}, s2 = {0};
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, &s1, sizeof(State));
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &s2, sizeof(State));
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
tox_node_bootstrap(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,300 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
uint32_t conference;
bool connected;
uint32_t peer_count;
bool n1_should_be_offline;
bool coordinator_should_be_offline;
} State;
static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->conference = tox_conference_join(tox_node_get_tox(self),
tox_event_conference_invite_get_friend_number(event),
tox_event_conference_invite_get_cookie(event),
tox_event_conference_invite_get_cookie_length(event),
nullptr);
tox_node_log(self, "Joined conference %u", state->conference);
}
static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->connected = true;
tox_node_log(self, "Connected to conference");
}
static void node_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
const Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_conference_invite(dispatch, on_conference_invite);
tox_events_callback_conference_connected(dispatch, on_conference_connected);
tox_node_log(self, "Waiting for self connection...");
tox_node_wait_for_self_connected(self);
tox_node_log(self, "Connected to DHT!");
// Wait for all nodes to be DHT-connected
for (int i = 0; i < 5; ++i) {
ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
WAIT_UNTIL(tox_node_is_self_connected(node));
}
tox_node_log(self, "All nodes connected to DHT!");
// Nodes 0, 1, 3, 4 just wait to be invited and connect.
// Coordination is handled by Node 2 (Founder/Coordinator).
tox_node_log(self, "Waiting for conference connection...");
WAIT_UNTIL(state->connected);
tox_node_log(self, "Connected to conference!");
// In this test, nodes participate in multiple phases.
// They just stay alive until the group is fully merged.
WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5);
tox_node_log(self, "Finished!");
}
static void coordinator_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_conference_invite(dispatch, on_conference_invite);
tox_events_callback_conference_connected(dispatch, on_conference_connected);
tox_node_log(self, "Waiting for self connection...");
tox_node_wait_for_self_connected(self);
tox_node_log(self, "Connected to DHT!");
// Wait for all nodes to be DHT-connected
for (int i = 0; i < 5; ++i) {
ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
WAIT_UNTIL(tox_node_is_self_connected(node));
}
tox_node_log(self, "All nodes connected to DHT!");
// 1. Create conference
state->conference = tox_conference_new(tox, nullptr);
state->connected = true;
tox_node_log(self, "Created conference %u", state->conference);
// 2. Invite Node 1
tox_node_log(self, "Waiting for friend 0 (Node1) to connect...");
tox_node_wait_for_friend_connected(self, 0); // Node 1 is friend 0
tox_node_log(self, "Friend 0 (Node1) connected! Inviting...");
tox_conference_invite(tox, 0, state->conference, nullptr);
const ToxNode *n1 = tox_scenario_get_node(tox_node_get_scenario(self), 1);
const State *s1_view = (const State *)tox_node_get_peer_ctx(n1);
WAIT_UNTIL(s1_view->connected);
// 3. Node 1 invites Node 0
// Wait for Node 0 to join conference
const ToxNode *n0 = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const State *s0_view = (const State *)tox_node_get_peer_ctx(n0);
tox_node_log(self, "Waiting for Node 0 to join conference...");
WAIT_UNTIL(s0_view->connected);
tox_node_log(self, "Node 0 joined!");
// 4. Split: Node 1 goes offline
tox_node_log(self, "Splitting group. Node 1 goes offline.");
state->n1_should_be_offline = true;
// Wait for Coordinator to see Node 1 is gone (Safe read via mirror)
tox_node_log(self, "Waiting for Node 1 to be seen as gone from conference...");
WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 2); // Only Coordinator and Node 0 left
// 5. Coordinator invites Node 3
tox_node_log(self, "Waiting for friend 1 (Node3) to connect...");
tox_node_wait_for_friend_connected(self, 1); // Node 3 is friend 1
tox_node_log(self, "Friend 1 (Node3) connected! Inviting...");
tox_conference_invite(tox, 1, state->conference, nullptr);
const ToxNode *n3 = tox_scenario_get_node(tox_node_get_scenario(self), 3);
const State *s3_view = (const State *)tox_node_get_peer_ctx(n3);
WAIT_UNTIL(s3_view->connected);
// 6. Node 3 invites Node 4
const ToxNode *n4 = tox_scenario_get_node(tox_node_get_scenario(self), 4);
const State *s4_view = (const State *)tox_node_get_peer_ctx(n4);
tox_node_log(self, "Waiting for Node 4 to join conference...");
WAIT_UNTIL(s4_view->connected);
tox_node_log(self, "Node 4 joined!");
// 7. Coordinator goes offline, Node 1 comes back online
tox_node_log(self, "Coordinator goes offline, Node 1 comes back online.");
state->n1_should_be_offline = false;
tox_node_set_offline(self, true);
// Wait in offline mode
for (int i = 0; i < 200; ++i) {
tox_scenario_yield(self);
}
// 8. Node 1 merges groups by inviting Node 3
// This is handled in Node 1's script after it observes n1_should_be_offline == false.
// 9. Coordinator comes back online
tox_node_log(self, "Coming back online.");
tox_node_set_offline(self, false);
// Coordinator rejoins
// In original test: reload and invite again.
// Here we just re-sync.
// Wait for all nodes to be in one group
WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5);
tox_node_log(self, "Group merged successfully!");
}
static void n1_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_conference_invite(dispatch, on_conference_invite);
tox_events_callback_conference_connected(dispatch, on_conference_connected);
tox_node_log(self, "Waiting for self connection...");
tox_node_wait_for_self_connected(self);
tox_node_log(self, "Connected to DHT!");
// Wait for all nodes to be DHT-connected
for (int i = 0; i < 5; ++i) {
ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
WAIT_UNTIL(tox_node_is_self_connected(node));
}
tox_node_log(self, "All nodes connected to DHT!");
// Phase 1: Wait to join
tox_node_log(self, "Waiting for conference connection...");
WAIT_UNTIL(state->connected);
tox_node_log(self, "Connected to conference!");
// Phase 2: Invite Node 0
tox_node_log(self, "Waiting for friend 0 (Node0) to connect...");
tox_node_wait_for_friend_connected(self, 0); // Node 0 is friend 0
tox_node_log(self, "Friend 0 (Node0) connected! Inviting...");
tox_conference_invite(tox, 0, state->conference, nullptr);
// Phase 3: Split (we will be set offline by Coordinator)
// Observe coordinator's request for our offline status
const ToxNode *coordinator = tox_scenario_get_node(tox_node_get_scenario(self), 2);
const State *coord_view = (const State *)tox_node_get_peer_ctx(coordinator);
WAIT_UNTIL(coord_view->n1_should_be_offline);
tox_node_log(self, "Going offline as requested by coordinator.");
tox_node_set_offline(self, true);
WAIT_UNTIL(!coord_view->n1_should_be_offline);
tox_node_log(self, "Coming back online as requested by coordinator.");
tox_node_set_offline(self, false);
// Phase 4: Merge groups
// Node 3 is friend 2 for Node 1
tox_node_log(self, "Waiting for friend 2 (Node3) to connect...");
tox_node_wait_for_friend_connected(self, 2);
tox_node_log(self, "Friend 2 (Node3) connected! Inviting to merge...");
tox_node_log(self, "Inviting Node 3 to merge groups.");
tox_conference_invite(tox, 2, state->conference, nullptr);
WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5);
tox_node_log(self, "Finished!");
}
static void n3_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_conference_invite(dispatch, on_conference_invite);
tox_events_callback_conference_connected(dispatch, on_conference_connected);
tox_node_log(self, "Waiting for self connection...");
tox_node_wait_for_self_connected(self);
tox_node_log(self, "Connected to DHT!");
// Wait for all nodes to be DHT-connected
for (int i = 0; i < 5; ++i) {
ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
WAIT_UNTIL(tox_node_is_self_connected(node));
}
tox_node_log(self, "All nodes connected to DHT!");
// Phase 2: Wait to join
tox_node_log(self, "Waiting for conference connection...");
WAIT_UNTIL(state->connected);
tox_node_log(self, "Connected to conference!");
// Phase 3: Invite Node 4
// Node 3 invites Node 4 when Coordinator is ready.
// We can just wait for Node 1 to go offline, which signals the split.
ToxNode *n1 = tox_scenario_get_node(tox_node_get_scenario(self), 1);
WAIT_UNTIL(tox_node_is_offline(n1));
tox_node_log(self, "Waiting for friend 1 (Node4) to connect...");
tox_node_wait_for_friend_connected(self, 1); // Node 4 is friend 1
tox_node_log(self, "Friend 1 (Node4) connected! Inviting...");
tox_conference_invite(tox, 1, state->conference, nullptr);
WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5);
tox_node_log(self, "Finished!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
State states[5] = {0};
ToxNode *nodes[5];
nodes[0] = tox_scenario_add_node(s, "Node0", node_script, &states[0], sizeof(State));
nodes[1] = tox_scenario_add_node(s, "Node1", n1_script, &states[1], sizeof(State));
nodes[2] = tox_scenario_add_node(s, "Coordinator", coordinator_script, &states[2], sizeof(State));
nodes[3] = tox_scenario_add_node(s, "Node3", n3_script, &states[3], sizeof(State));
nodes[4] = tox_scenario_add_node(s, "Node4", node_script, &states[4], sizeof(State));
// Topology: 0-1-2-3-4
tox_node_friend_add(nodes[0], nodes[1]);
tox_node_friend_add(nodes[1], nodes[0]);
tox_node_friend_add(nodes[1], nodes[2]);
tox_node_friend_add(nodes[2], nodes[1]);
tox_node_friend_add(nodes[2], nodes[3]);
tox_node_friend_add(nodes[3], nodes[2]);
tox_node_friend_add(nodes[3], nodes[4]);
tox_node_friend_add(nodes[4], nodes[3]);
// Merge connection: 1-3
tox_node_friend_add(nodes[1], nodes[3]);
tox_node_friend_add(nodes[3], nodes[1]);
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) {
if (i != j) {
tox_node_bootstrap(nodes[i], nodes[j]);
}
}
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,132 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
uint32_t conf_num;
bool bob_joined;
} AliceState;
static void alice_on_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
AliceState *state = (AliceState *)tox_node_get_script_ctx(self);
uint32_t conf_num = tox_event_conference_peer_list_changed_get_conference_number(event);
if (conf_num != state->conf_num) {
return;
}
uint32_t count = tox_conference_peer_count(tox_node_get_tox(self), conf_num, nullptr);
if (count == 2) {
state->bob_joined = true;
}
}
static void alice_script(ToxNode *self, void *ctx)
{
AliceState *state = (AliceState *)ctx;
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), alice_on_peer_list_changed);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
Tox *tox = tox_node_get_tox(self);
Tox_Err_Conference_New err_new;
state->conf_num = tox_conference_new(tox, &err_new);
ck_assert(err_new == TOX_ERR_CONFERENCE_NEW_OK);
tox_conference_set_max_offline(tox, state->conf_num, 10, nullptr);
tox_node_log(self, "Inviting Bob to conference %u", state->conf_num);
tox_conference_invite(tox, 0, state->conf_num, nullptr);
WAIT_UNTIL(state->bob_joined);
tox_node_log(self, "Bob joined. Reloading Alice...");
tox_node_reload(self);
tox_node_log(self, "Alice reloaded.");
// Re-register callback after reload
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), alice_on_peer_list_changed);
// After reload, we need to get the conference number again
uint32_t chatlist[1];
tox_conference_get_chatlist(tox_node_get_tox(self), chatlist);
state->conf_num = chatlist[0];
tox_node_log(self, "Checking offline peer list for conference %u...", state->conf_num);
Tox_Err_Conference_Peer_Query q_err;
uint32_t offline_count = tox_conference_offline_peer_count(tox_node_get_tox(self), state->conf_num, &q_err);
ck_assert(q_err == TOX_ERR_CONFERENCE_PEER_QUERY_OK);
tox_node_log(self, "Offline peer count: %u", offline_count);
// Bob should be offline now because we just started and haven't connected to him in the conference yet.
ck_assert(offline_count >= 1);
tox_node_log(self, "Conference offline peer tests passed!");
}
typedef struct {
bool invited;
uint32_t conf_num;
uint8_t cookie[512];
size_t cookie_len;
} BobState;
static void bob_on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
BobState *state = (BobState *)tox_node_get_script_ctx(self);
state->invited = true;
state->cookie_len = tox_event_conference_invite_get_cookie_length(event);
memcpy(state->cookie, tox_event_conference_invite_get_cookie(event), state->cookie_len);
tox_node_log(self, "Received conference invite");
}
static void bob_script(ToxNode *self, void *ctx)
{
const BobState *state = (const BobState *)ctx;
tox_events_callback_conference_invite(tox_node_get_dispatch(self), bob_on_conference_invite);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
WAIT_UNTIL(state->invited);
tox_conference_join(tox_node_get_tox(self), 0, state->cookie, state->cookie_len, nullptr);
ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0);
WAIT_UNTIL(tox_node_is_finished(alice));
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
AliceState alice_state = {0};
BobState bob_state = {0};
tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState));
tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState));
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,111 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
bool joined;
uint32_t conference;
bool friend_in_group;
} State;
static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
Tox_Err_Conference_Join err;
state->conference = tox_conference_join(tox_node_get_tox(self),
tox_event_conference_invite_get_friend_number(event),
tox_event_conference_invite_get_cookie(event),
tox_event_conference_invite_get_cookie_length(event),
&err);
if (err == TOX_ERR_CONFERENCE_JOIN_OK) {
state->joined = true;
}
}
static void on_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
uint32_t count = tox_conference_peer_count(tox_node_get_tox(self), tox_event_conference_peer_list_changed_get_conference_number(event), nullptr);
state->friend_in_group = (count == 2);
}
static void alice_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
State *state = (State *)ctx;
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_peer_list_changed);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_self_set_name(tox, (const uint8_t *)"Alice", 5, nullptr);
tox_node_log(self, "Creating conference...");
Tox_Err_Conference_New err_new;
state->conference = tox_conference_new(tox, &err_new);
state->joined = true;
tox_node_log(self, "Inviting Bob...");
tox_conference_invite(tox, 0, state->conference, nullptr);
tox_node_log(self, "Waiting for Bob to join...");
WAIT_UNTIL(state->friend_in_group);
tox_node_log(self, "Bob joined!");
// Wait for Bob to drop out (simulated by Bob finishing)
tox_node_log(self, "Waiting for Bob to leave...");
WAIT_UNTIL(!state->friend_in_group);
tox_node_log(self, "Bob left!");
}
static void bob_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
const State *state = (const State *)ctx;
tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite);
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_peer_list_changed);
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_self_set_name(tox, (const uint8_t *)"Bob", 3, nullptr);
WAIT_UNTIL(state->joined);
tox_node_log(self, "Joined conference!");
WAIT_UNTIL(state->friend_in_group);
// Stay a bit then leave
for (int i = 0; i < 20; ++i) {
tox_scenario_yield(self);
}
tox_node_log(self, "Leaving conference...");
tox_conference_delete(tox, state->conference, nullptr);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
State s1 = {0}, s2 = {0};
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, &s1, sizeof(State));
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &s2, sizeof(State));
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
tox_node_bootstrap(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,131 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
uint32_t conf_num;
bool peer_joined;
} AliceState;
static void on_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
AliceState *state = (AliceState *)tox_node_get_script_ctx(self);
if (tox_conference_peer_count(tox_node_get_tox(self), state->conf_num, nullptr) == 2) {
state->peer_joined = true;
}
}
static void alice_script(ToxNode *self, void *ctx)
{
AliceState *state = (AliceState *)ctx;
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_peer_list_changed);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
Tox *tox = tox_node_get_tox(self);
Tox_Err_Conference_New err_new;
state->conf_num = tox_conference_new(tox, &err_new);
ck_assert(err_new == TOX_ERR_CONFERENCE_NEW_OK);
tox_node_log(self, "Created conference %u", state->conf_num);
tox_conference_invite(tox, 0, state->conf_num, nullptr);
WAIT_UNTIL(state->peer_joined);
tox_node_log(self, "Bob joined. Querying peer info...");
// Test tox_conference_peer_number_is_ours
// Our own peer number should be 0 or 1.
Tox_Err_Conference_Peer_Query q_err;
bool p0_ours = tox_conference_peer_number_is_ours(tox, state->conf_num, 0, &q_err);
ck_assert(q_err == TOX_ERR_CONFERENCE_PEER_QUERY_OK);
bool p1_ours = tox_conference_peer_number_is_ours(tox, state->conf_num, 1, &q_err);
ck_assert(q_err == TOX_ERR_CONFERENCE_PEER_QUERY_OK);
ck_assert(p0_ours != p1_ours); // One must be ours, the other must not be.
uint32_t our_peer_num = p0_ours ? 0 : 1;
uint32_t bobs_peer_num = p0_ours ? 1 : 0;
// Test tox_conference_peer_get_public_key
uint8_t our_pk[TOX_PUBLIC_KEY_SIZE];
uint8_t our_self_pk[TOX_PUBLIC_KEY_SIZE];
tox_conference_peer_get_public_key(tox, state->conf_num, our_peer_num, our_pk, nullptr);
tox_self_get_public_key(tox, our_self_pk);
ck_assert(memcmp(our_pk, our_self_pk, TOX_PUBLIC_KEY_SIZE) == 0);
uint8_t bobs_pk[TOX_PUBLIC_KEY_SIZE];
uint8_t bobs_self_pk[TOX_PUBLIC_KEY_SIZE];
tox_conference_peer_get_public_key(tox, state->conf_num, bobs_peer_num, bobs_pk, nullptr);
ToxNode *bob_node = tox_scenario_get_node(tox_node_get_scenario(self), 1);
tox_self_get_public_key(tox_node_get_tox(bob_node), bobs_self_pk);
ck_assert(memcmp(bobs_pk, bobs_self_pk, TOX_PUBLIC_KEY_SIZE) == 0);
// Test tox_conference_get_type
Tox_Err_Conference_Get_Type gt_err;
Tox_Conference_Type type = tox_conference_get_type(tox, state->conf_num, &gt_err);
ck_assert(gt_err == TOX_ERR_CONFERENCE_GET_TYPE_OK);
ck_assert(type == TOX_CONFERENCE_TYPE_TEXT);
tox_node_log(self, "Conference query tests passed!");
}
typedef struct {
bool invited;
uint8_t cookie[512];
size_t cookie_len;
} BobState;
static void bob_on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
BobState *state = (BobState *)tox_node_get_script_ctx(self);
state->invited = true;
state->cookie_len = tox_event_conference_invite_get_cookie_length(event);
memcpy(state->cookie, tox_event_conference_invite_get_cookie(event), state->cookie_len);
}
static void bob_script(ToxNode *self, void *ctx)
{
const BobState *state = (const BobState *)ctx;
tox_events_callback_conference_invite(tox_node_get_dispatch(self), bob_on_conference_invite);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
WAIT_UNTIL(state->invited);
tox_conference_join(tox_node_get_tox(self), 0, state->cookie, state->cookie_len, nullptr);
ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0);
WAIT_UNTIL(tox_node_is_finished(alice));
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
AliceState alice_state = {0, false};
BobState bob_state = {0};
tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState));
tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState));
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,155 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
typedef struct {
bool joined;
bool connected;
uint32_t conference;
bool received;
uint32_t peers;
} State;
static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->connected = true;
tox_node_log(self, "Connected to conference %u", tox_event_conference_connected_get_conference_number(event));
}
static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
tox_node_log(self, "Received conference invite from friend %u", tox_event_conference_invite_get_friend_number(event));
Tox_Err_Conference_Join err;
state->conference = tox_conference_join(tox_node_get_tox(self),
tox_event_conference_invite_get_friend_number(event),
tox_event_conference_invite_get_cookie(event),
tox_event_conference_invite_get_cookie_length(event),
&err);
if (err == TOX_ERR_CONFERENCE_JOIN_OK) {
state->joined = true;
tox_node_log(self, "Joined conference %u", state->conference);
} else {
tox_node_log(self, "Failed to join conference: %u", err);
}
}
static void on_conference_message(const Tox_Event_Conference_Message *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
const uint8_t *msg = tox_event_conference_message_get_message(event);
if (memcmp(msg, "hello!", 6) == 0) {
state->received = true;
}
}
static void on_conference_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->peers = tox_conference_peer_count(tox_node_get_tox(self), tox_event_conference_peer_list_changed_get_conference_number(event), nullptr);
}
static void tox1_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
State *state = (State *)ctx;
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_conference_peer_list_changed);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Creating conference...");
Tox_Err_Conference_New err_new;
state->conference = tox_conference_new(tox, &err_new);
state->joined = true;
tox_node_log(self, "Inviting Bob...");
Tox_Err_Conference_Invite err_inv;
tox_conference_invite(tox, 0, state->conference, &err_inv);
WAIT_UNTIL(state->peers == 3);
tox_node_log(self, "All peers joined!");
Tox_Err_Conference_Send_Message err_msg;
tox_conference_send_message(tox, state->conference, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello!", 6, &err_msg);
}
static void tox2_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
State *state = (State *)ctx;
tox_events_callback_conference_connected(tox_node_get_dispatch(self), on_conference_connected);
tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite);
tox_events_callback_conference_message(tox_node_get_dispatch(self), on_conference_message);
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_conference_peer_list_changed);
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Waiting for join...");
WAIT_UNTIL(state->joined);
tox_node_log(self, "Joined conference %u. Waiting for connection...", state->conference);
WAIT_UNTIL(state->connected);
tox_node_log(self, "Connected. Waiting for Charlie to connect...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 1));
tox_node_log(self, "Charlie connected. Inviting Charlie...");
Tox_Err_Conference_Invite err_inv;
bool ok = tox_conference_invite(tox, 1, state->conference, &err_inv);
if (ok) {
tox_node_log(self, "Successfully invited Charlie");
} else {
tox_node_log(self, "Failed to invite Charlie: %u", err_inv);
}
WAIT_UNTIL(state->received);
tox_node_log(self, "Received message!");
}
static void tox3_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite);
tox_events_callback_conference_message(tox_node_get_dispatch(self), on_conference_message);
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_conference_peer_list_changed);
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Waiting for message...");
WAIT_UNTIL(state->received);
tox_node_log(self, "Received message!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 600000); // 10m virtual timeout
State s1 = {0}, s2 = {0}, s3 = {0};
ToxNode *t1 = tox_scenario_add_node(s, "Alice", tox1_script, &s1, sizeof(State));
ToxNode *t2 = tox_scenario_add_node(s, "Bob", tox2_script, &s2, sizeof(State));
ToxNode *t3 = tox_scenario_add_node(s, "Charlie", tox3_script, &s3, sizeof(State));
// t1 <-> t2, t2 <-> t3
tox_node_friend_add(t1, t2);
tox_node_friend_add(t2, t1);
tox_node_friend_add(t2, t3);
tox_node_friend_add(t3, t2);
tox_node_bootstrap(t2, t1);
tox_node_bootstrap(t3, t1); // All bootstrap from Alice
tox_node_bootstrap(t3, t2);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,148 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_PEERS 16
#define GROUP_MESSAGE "Install Gentoo"
typedef struct {
bool joined;
bool connected;
uint32_t conference;
uint32_t peer_count;
uint32_t messages_received;
} PeerState;
static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
PeerState *state = (PeerState *)tox_node_get_script_ctx(self);
state->conference = tox_conference_join(tox_node_get_tox(self),
tox_event_conference_invite_get_friend_number(event),
tox_event_conference_invite_get_cookie(event),
tox_event_conference_invite_get_cookie_length(event),
nullptr);
tox_node_log(self, "Joined conference %u", state->conference);
state->joined = true;
}
static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
PeerState *state = (PeerState *)tox_node_get_script_ctx(self);
state->connected = true;
tox_node_log(self, "Connected to conference %u", tox_event_conference_connected_get_conference_number(event));
}
static void on_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
PeerState *state = (PeerState *)tox_node_get_script_ctx(self);
state->peer_count = tox_conference_peer_count(tox_node_get_tox(self), tox_event_conference_peer_list_changed_get_conference_number(event), nullptr);
tox_node_log(self, "Peer count changed: %u", state->peer_count);
}
static void on_conference_message(const Tox_Event_Conference_Message *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
PeerState *state = (PeerState *)tox_node_get_script_ctx(self);
const uint8_t *msg = tox_event_conference_message_get_message(event);
if (memcmp(msg, GROUP_MESSAGE, sizeof(GROUP_MESSAGE) - 1) == 0) {
state->messages_received++;
tox_node_log(self, "Received message %u", state->messages_received);
}
}
static void peer_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
PeerState *state = (PeerState *)ctx;
uint32_t my_index = tox_node_get_index(self);
tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite);
tox_events_callback_conference_connected(tox_node_get_dispatch(self), on_conference_connected);
tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_peer_list_changed);
tox_events_callback_conference_message(tox_node_get_dispatch(self), on_conference_message);
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Self connected");
// Founder (Node 0) creates group
if (my_index == 0) {
state->conference = tox_conference_new(tox, nullptr);
tox_node_log(self, "Created conference %u", state->conference);
state->joined = true;
state->connected = true;
// Invite friend 0 (Node 1)
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Friend 0 (Node 1) connected, inviting...");
tox_conference_invite(tox, 0, state->conference, nullptr);
} else {
WAIT_UNTIL(state->joined);
WAIT_UNTIL(state->connected);
// Invite next friends if any
// In linear topology, each node has 2 friends (except ends)
// We invite friend 1 (the next node in line)
if (my_index < NUM_PEERS - 1) {
WAIT_UNTIL(tox_node_is_friend_connected(self, 1));
tox_node_log(self, "Friend 1 (Node %u) connected, inviting...", my_index + 1);
Tox_Err_Conference_Invite err;
if (!tox_conference_invite(tox, 1, state->conference, &err)) {
tox_node_log(self, "Failed to invite Node %u: %u", my_index + 1, err);
}
}
}
// Wait for all to join
WAIT_UNTIL(state->peer_count == NUM_PEERS);
tox_node_log(self, "All peers joined");
// One peer sends a message
if (my_index == 0) {
tox_node_log(self, "Sending message");
tox_conference_send_message(tox, state->conference, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)GROUP_MESSAGE, sizeof(GROUP_MESSAGE) - 1, nullptr);
}
// Wait for message propagation
WAIT_UNTIL(state->messages_received > 0);
tox_node_log(self, "Done");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
PeerState states[NUM_PEERS] = {0};
ToxNode *nodes[NUM_PEERS];
for (int i = 0; i < NUM_PEERS; ++i) {
char alias[16];
snprintf(alias, sizeof(alias), "Peer%d", i);
nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(PeerState));
}
// Linear topology
for (int i = 0; i < NUM_PEERS; ++i) {
if (i > 0) {
tox_node_friend_add(nodes[i], nodes[i - 1]);
tox_node_bootstrap(nodes[i], nodes[i - 1]);
}
if (i < NUM_PEERS - 1) {
tox_node_friend_add(nodes[i], nodes[i + 1]);
}
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef NUM_PEERS

View File

@@ -0,0 +1,38 @@
#include "framework/framework.h"
#include <stdio.h>
static void tox_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
tox_node_log(self, "Creating conference 1...");
Tox_Err_Conference_New err;
tox_conference_new(tox, &err);
if (err != TOX_ERR_CONFERENCE_NEW_OK) {
tox_node_log(self, "Failed to create conference 1: %u", err);
return;
}
tox_node_log(self, "Creating conference 2...");
tox_conference_new(tox, &err);
if (err != TOX_ERR_CONFERENCE_NEW_OK) {
tox_node_log(self, "Failed to create conference 2: %u", err);
return;
}
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
tox_scenario_add_node(s, "Node", tox_script, nullptr, 0);
// Just run it until completion
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,195 @@
#include "framework/framework.h"
#include "../../toxcore/tox_private.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_TOXES 30
typedef struct {
uint8_t public_key[TOX_DHT_NODE_PUBLIC_KEY_SIZE];
char ip[TOX_DHT_NODE_IP_STRING_SIZE];
uint16_t port;
} Dht_Node;
typedef struct {
Dht_Node *nodes[NUM_TOXES];
size_t num_nodes;
uint8_t public_key_list[NUM_TOXES][TOX_PUBLIC_KEY_SIZE];
} State;
static bool node_crawled(const State *state, const uint8_t *public_key)
{
for (size_t i = 0; i < state->num_nodes; ++i) {
if (memcmp(state->nodes[i]->public_key, public_key, TOX_DHT_NODE_PUBLIC_KEY_SIZE) == 0) {
return true;
}
}
return false;
}
static void on_dht_nodes_response(const Tox_Event_Dht_Nodes_Response *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
const uint8_t *public_key = tox_event_dht_nodes_response_get_public_key(event);
const char *ip = (const char *)tox_event_dht_nodes_response_get_ip(event);
const uint16_t port = tox_event_dht_nodes_response_get_port(event);
if (node_crawled(state, public_key)) {
return;
}
if (state->num_nodes >= NUM_TOXES) {
return;
}
Dht_Node *node = (Dht_Node *)calloc(1, sizeof(Dht_Node));
ck_assert(node != nullptr);
memcpy(node->public_key, public_key, TOX_DHT_NODE_PUBLIC_KEY_SIZE);
snprintf(node->ip, sizeof(node->ip), "%s", ip);
node->port = port;
state->nodes[state->num_nodes] = node;
++state->num_nodes;
// ask new node to give us their close nodes to every public key
for (size_t i = 0; i < NUM_TOXES; ++i) {
tox_dht_send_nodes_request(tox_node_get_tox(self), public_key, ip, port, state->public_key_list[i], nullptr);
}
}
static void peer_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
tox_events_callback_dht_nodes_response(tox_node_get_dispatch(self), on_dht_nodes_response);
// Initial bootstrap: ask the node we bootstrapped from for all other nodes
// Wait for self connection first
WAIT_UNTIL(tox_node_is_self_connected(self));
// After connecting, we should start receiving responses because we bootstrapped
// but the original test calls tox_dht_send_nodes_request for all nodes.
// In our case, we bootstrap linearly, so Peer-i bootstraps from Peer-(i-1).
// Peer-0 doesn't bootstrap from anyone.
// To kick off crawling, each node can ask its bootstrap source for all nodes.
uint32_t my_index = tox_node_get_index(self);
if (my_index > 0) {
ToxNode *bootstrap_source = tox_scenario_get_node(tox_node_get_scenario(self), my_index - 1);
uint8_t bs_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox_node_get_tox(bootstrap_source), bs_pk);
Tox_Err_Get_Port port_err;
uint16_t bs_port = tox_self_get_udp_port(tox_node_get_tox(bootstrap_source), &port_err);
for (size_t i = 0; i < NUM_TOXES; ++i) {
tox_dht_send_nodes_request(tox_node_get_tox(self), bs_pk, "127.0.0.1", bs_port, state->public_key_list[i], nullptr);
}
}
// Wait until we have crawled all nodes
uint64_t last_log_time = 0;
uint64_t last_request_time = 0;
while (state->num_nodes < NUM_TOXES) {
tox_scenario_yield(self);
uint64_t now = tox_scenario_get_time(tox_node_get_scenario(self));
if (now - last_request_time >= 2000) {
last_request_time = now;
// Retry strategy: ask a random known node for missing nodes
if (state->num_nodes > 0) {
int idx = rand() % state->num_nodes;
const Dht_Node *n = state->nodes[idx];
for (int i = 0; i < NUM_TOXES; ++i) {
if (!node_crawled(state, state->public_key_list[i])) {
tox_dht_send_nodes_request(tox_node_get_tox(self),
n->public_key, n->ip, n->port, state->public_key_list[i], nullptr);
}
}
} else if (my_index > 0) {
// Fallback: if we haven't found anyone yet, ask bootstrap peer again
ToxNode *bootstrap_source = tox_scenario_get_node(tox_node_get_scenario(self), my_index - 1);
uint8_t bs_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox_node_get_tox(bootstrap_source), bs_pk);
Tox_Err_Get_Port port_err;
uint16_t bs_port = tox_self_get_udp_port(tox_node_get_tox(bootstrap_source), &port_err);
for (int i = 0; i < NUM_TOXES; ++i) {
tox_dht_send_nodes_request(tox_node_get_tox(self), bs_pk, "127.0.0.1", bs_port, state->public_key_list[i], nullptr);
}
}
}
if (now - last_log_time >= 5000) {
last_log_time = now;
tox_node_log(self, "Still crawling... found %zu/%d nodes", state->num_nodes, NUM_TOXES);
if (state->num_nodes < NUM_TOXES) {
char missing[256] = {0};
size_t pos = 0;
for (int i = 0; i < NUM_TOXES; ++i) {
if (!node_crawled(state, state->public_key_list[i])) {
pos += snprintf(missing + pos, sizeof(missing) - pos, "%d ", i);
if (pos >= sizeof(missing) - 1) {
break;
}
}
}
tox_node_log(self, "Missing nodes: %s", missing);
}
}
}
tox_node_log(self, "Finished crawling all %u nodes.", (unsigned int)state->num_nodes);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
State *states = (State *)calloc(NUM_TOXES, sizeof(State));
ToxNode *nodes[NUM_TOXES];
for (uint32_t i = 0; i < NUM_TOXES; ++i) {
char alias[32];
snprintf(alias, sizeof(alias), "Peer-%u", i);
nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(State));
}
// Fill the public_key_list for all nodes
uint8_t public_keys[NUM_TOXES][TOX_PUBLIC_KEY_SIZE];
for (uint32_t i = 0; i < NUM_TOXES; ++i) {
tox_self_get_dht_id(tox_node_get_tox(nodes[i]), public_keys[i]);
}
for (uint32_t i = 0; i < NUM_TOXES; ++i) {
memcpy(states[i].public_key_list, public_keys, sizeof(public_keys));
}
// Create a linear graph
for (uint32_t i = 1; i < NUM_TOXES; ++i) {
tox_node_bootstrap(nodes[i], nodes[i - 1]);
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
for (uint32_t i = 0; i < NUM_TOXES; ++i) {
for (size_t j = 0; j < states[i].num_nodes; ++j) {
free(states[i].nodes[j]);
}
}
free(states);
tox_scenario_free(s);
return 0;
}
#undef NUM_TOXES

View File

@@ -0,0 +1,46 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void alice_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
tox_node_log(self, "Sending message to Bob...");
uint8_t msg[] = "hello";
Tox_Err_Friend_Send_Message err;
tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err);
ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK);
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
tox_node_log(self, "Waiting for message from Alice...");
WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE);
tox_node_log(self, "Received message!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,81 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
bool cancelled;
} AliceState;
static void on_file_recv_control(const Tox_Event_File_Recv_Control *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
AliceState *state = (AliceState *)tox_node_get_script_ctx(self);
if (tox_event_file_recv_control_get_control(event) == TOX_FILE_CONTROL_CANCEL) {
state->cancelled = true;
tox_node_log(self, "Bob cancelled the file transfer.");
}
}
static void alice_script(ToxNode *self, void *ctx)
{
const AliceState *state = (const AliceState *)ctx;
tox_events_callback_file_recv_control(tox_node_get_dispatch(self), on_file_recv_control);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
Tox *tox = tox_node_get_tox(self);
tox_file_send(tox, 0, TOX_FILE_KIND_DATA, 1000, nullptr, (const uint8_t *)"test.txt", 8, nullptr);
WAIT_UNTIL(state->cancelled);
tox_node_log(self, "File cancellation verified from Alice side.");
}
static void on_file_recv(const Tox_Event_File_Recv *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
uint32_t friend_number = tox_event_file_recv_get_friend_number(event);
uint32_t file_number = tox_event_file_recv_get_file_number(event);
tox_node_log(self, "Received file request, rejecting (CANCEL)...");
tox_file_control(tox_node_get_tox(self), friend_number, file_number, TOX_FILE_CONTROL_CANCEL, nullptr);
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_events_callback_file_recv(tox_node_get_dispatch(self), on_file_recv);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0);
WAIT_UNTIL(tox_node_is_finished(alice));
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
AliceState alice_state = {false};
tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState));
tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,134 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILE_SIZE (1024 * 1024)
#define SEEK_POS (512 * 1024)
typedef struct {
bool seek_happened;
bool finished;
} SenderState;
static void on_file_chunk_request(const Tox_Event_File_Chunk_Request *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
SenderState *state = (SenderState *)tox_node_get_script_ctx(self);
uint32_t friend_number = tox_event_file_chunk_request_get_friend_number(event);
uint32_t file_number = tox_event_file_chunk_request_get_file_number(event);
uint64_t position = tox_event_file_chunk_request_get_position(event);
size_t length = tox_event_file_chunk_request_get_length(event);
if (length == 0) {
state->finished = true;
return;
}
if (position >= SEEK_POS) {
state->seek_happened = true;
}
uint8_t data[TOX_MAX_CUSTOM_PACKET_SIZE];
memset(data, (uint8_t)(position % 256), length);
tox_file_send_chunk(tox_node_get_tox(self), friend_number, file_number, position, data, length, nullptr);
}
static void sender_script(ToxNode *self, void *ctx)
{
SenderState *state = (SenderState *)ctx;
tox_events_callback_file_chunk_request(tox_node_get_dispatch(self), on_file_chunk_request);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
Tox *tox = tox_node_get_tox(self);
tox_file_send(tox, 0, TOX_FILE_KIND_DATA, FILE_SIZE, nullptr, (const uint8_t *)"seek_test.bin", 13, nullptr);
WAIT_UNTIL(state->finished);
tox_node_log(self, "Sender finished. Seek happened: %s", state->seek_happened ? "YES" : "NO");
ck_assert(state->seek_happened);
}
typedef struct {
uint32_t file_number;
bool file_recv_called;
bool finished;
} ReceiverState;
static void on_file_recv(const Tox_Event_File_Recv *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
ReceiverState *state = (ReceiverState *)tox_node_get_script_ctx(self);
state->file_number = tox_event_file_recv_get_file_number(event);
state->file_recv_called = true;
tox_node_log(self, "Received file request, file_number = %u", state->file_number);
}
static void on_file_recv_chunk(const Tox_Event_File_Recv_Chunk *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
ReceiverState *state = (ReceiverState *)tox_node_get_script_ctx(self);
size_t length = tox_event_file_recv_chunk_get_data_length(event);
if (length == 0) {
state->finished = true;
}
}
static void receiver_script(ToxNode *self, void *ctx)
{
ReceiverState *state = (ReceiverState *)ctx;
tox_events_callback_file_recv(tox_node_get_dispatch(self), on_file_recv);
tox_events_callback_file_recv_chunk(tox_node_get_dispatch(self), on_file_recv_chunk);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
WAIT_UNTIL(state->file_recv_called);
tox_node_log(self, "Seeking to %d before resuming...", SEEK_POS);
Tox_Err_File_Seek seek_err;
bool seek_res = tox_file_seek(tox_node_get_tox(self), 0, state->file_number, SEEK_POS, &seek_err);
if (!seek_res) {
tox_node_log(self, "tox_file_seek failed: %s", tox_err_file_seek_to_string(seek_err));
}
ck_assert(seek_res);
tox_node_log(self, "Resuming file %u...", state->file_number);
Tox_Err_File_Control ctrl_err;
bool resume_res = tox_file_control(tox_node_get_tox(self), 0, state->file_number, TOX_FILE_CONTROL_RESUME, &ctrl_err);
ck_assert(resume_res);
WAIT_UNTIL(state->finished);
tox_node_log(self, "Receiver finished!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
SenderState sender_state = {false, false};
ReceiverState receiver_state = {0, false, false};
tox_scenario_add_node(s, "Sender", sender_script, &sender_state, sizeof(SenderState));
tox_scenario_add_node(s, "Receiver", receiver_script, &receiver_state, sizeof(ReceiverState));
ToxNode *sender = tox_scenario_get_node(s, 0);
ToxNode *receiver = tox_scenario_get_node(s, 1);
tox_node_bootstrap(sender, receiver);
tox_node_friend_add(sender, receiver);
tox_node_friend_add(receiver, sender);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef FILE_SIZE

View File

@@ -0,0 +1,150 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define FILE_SIZE (1024 * 1024)
#define FILENAME "Gentoo.exe"
typedef struct {
bool file_received;
bool file_sending_done;
uint64_t size_recv;
uint64_t sending_pos;
uint8_t file_id[TOX_FILE_ID_LENGTH];
uint32_t file_accepted;
uint64_t file_size;
bool sendf_ok;
uint8_t num;
uint8_t sending_num;
bool m_send_reached;
} FileTransferState;
static void on_file_receive(const Tox_Event_File_Recv *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
FileTransferState *state = (FileTransferState *)tox_node_get_script_ctx(self);
const uint32_t friend_number = tox_event_file_recv_get_friend_number(event);
const uint32_t file_number = tox_event_file_recv_get_file_number(event);
const uint64_t filesize = tox_event_file_recv_get_file_size(event);
state->file_size = filesize;
state->sending_pos = state->size_recv = 0;
Tox_Err_File_Control error;
tox_file_control(tox_node_get_tox(self), friend_number, file_number, TOX_FILE_CONTROL_RESUME, &error);
state->file_accepted++;
tox_node_log(self, "File receive: %u bytes, accepted", (uint32_t)filesize);
}
static void on_file_recv_control(const Tox_Event_File_Recv_Control *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
FileTransferState *state = (FileTransferState *)tox_node_get_script_ctx(self);
const Tox_File_Control control = tox_event_file_recv_control_get_control(event);
if (control == TOX_FILE_CONTROL_RESUME) {
state->sendf_ok = true;
tox_node_log(self, "Received RESUME control");
}
}
static void on_file_chunk_request(const Tox_Event_File_Chunk_Request *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
FileTransferState *state = (FileTransferState *)tox_node_get_script_ctx(self);
const uint32_t friend_number = tox_event_file_chunk_request_get_friend_number(event);
const uint32_t file_number = tox_event_file_chunk_request_get_file_number(event);
const uint64_t position = tox_event_file_chunk_request_get_position(event);
size_t length = tox_event_file_chunk_request_get_length(event);
if (length == 0) {
state->file_sending_done = true;
tox_node_log(self, "File sending done");
return;
}
uint8_t *f_data = (uint8_t *)malloc(length);
memset(f_data, state->sending_num, length);
tox_file_send_chunk(tox_node_get_tox(self), friend_number, file_number, position, f_data, length, nullptr);
free(f_data);
state->sending_num++;
state->sending_pos += length;
}
static void on_file_recv_chunk(const Tox_Event_File_Recv_Chunk *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
FileTransferState *state = (FileTransferState *)tox_node_get_script_ctx(self);
const size_t length = tox_event_file_recv_chunk_get_data_length(event);
if (length == 0) {
state->file_received = true;
tox_node_log(self, "File reception done");
return;
}
state->num++;
state->size_recv += length;
}
static void sender_script(ToxNode *self, void *ctx)
{
const FileTransferState *state = (const FileTransferState *)ctx;
tox_events_callback_file_recv_control(tox_node_get_dispatch(self), on_file_recv_control);
tox_events_callback_file_chunk_request(tox_node_get_dispatch(self), on_file_chunk_request);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
uint32_t fnum = tox_file_send(tox_node_get_tox(self), 0, TOX_FILE_KIND_DATA, FILE_SIZE, nullptr, (const uint8_t *)FILENAME, sizeof(FILENAME), nullptr);
tox_node_log(self, "Started sending file %u", fnum);
WAIT_UNTIL(state->file_sending_done);
tox_node_log(self, "Done");
}
static void receiver_script(ToxNode *self, void *ctx)
{
const FileTransferState *state = (const FileTransferState *)ctx;
tox_events_callback_file_recv(tox_node_get_dispatch(self), on_file_receive);
tox_events_callback_file_recv_chunk(tox_node_get_dispatch(self), on_file_recv_chunk);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(state->file_received);
tox_node_log(self, "Done");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
FileTransferState state_sender = {0};
FileTransferState state_receiver = {0};
ToxNode *sender = tox_scenario_add_node(s, "Sender", sender_script, &state_sender, sizeof(FileTransferState));
ToxNode *receiver = tox_scenario_add_node(s, "Receiver", receiver_script, &state_receiver, sizeof(FileTransferState));
tox_node_friend_add(sender, receiver);
tox_node_friend_add(receiver, sender);
tox_node_bootstrap(sender, receiver);
tox_node_bootstrap(receiver, sender);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef FILE_SIZE

View File

@@ -0,0 +1,46 @@
#include "framework/framework.h"
#include <stdio.h>
static void alice_script(ToxNode *self, void *ctx)
{
tox_node_log(self, "Waiting for DHT connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Connected to DHT. Waiting for friend connection...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to Bob!");
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_log(self, "Waiting for DHT connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Connected to DHT. Waiting for friend connection...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to Alice!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000); // 60s virtual timeout
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
tox_scenario_log(s, "Starting scenario...");
ToxScenarioStatus res = tox_scenario_run(s);
if (res == TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Scenario completed successfully!");
} else {
tox_scenario_log(s, "Scenario failed with status: %u", res);
}
tox_scenario_free(s);
return (res == TOX_SCENARIO_DONE) ? 0 : 1;
}

View File

@@ -0,0 +1,103 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
bool bob_went_offline;
} AliceState;
static void on_alice_friend_connection_status(const Tox_Event_Friend_Connection_Status *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
AliceState *state = (AliceState *)tox_node_get_script_ctx(self);
Tox_Connection status = tox_event_friend_connection_status_get_connection_status(event);
tox_node_log(self, "Friend connection status changed to %u", status);
if (status == TOX_CONNECTION_NONE) {
state->bob_went_offline = true;
}
}
static void alice_script(ToxNode *self, void *ctx)
{
tox_events_callback_friend_connection_status(tox_node_get_dispatch(self), on_alice_friend_connection_status);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
tox_node_log(self, "Connected to Bob. Deleting Bob now...");
Tox_Err_Friend_Delete err;
bool success = tox_friend_delete(tox_node_get_tox(self), 0, &err);
ck_assert(success);
ck_assert(err == TOX_ERR_FRIEND_DELETE_OK);
tox_node_log(self, "Bob deleted. Bob should see Alice as offline.");
// Wait for Bob to finish
ToxNode *bob = tox_scenario_get_node(tox_node_get_scenario(self), 1);
WAIT_UNTIL(tox_node_is_finished(bob));
}
typedef struct {
bool alice_went_offline;
bool alice_connected;
} BobState;
static void on_bob_friend_connection_status(const Tox_Event_Friend_Connection_Status *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
BobState *state = (BobState *)tox_node_get_script_ctx(self);
Tox_Connection status = tox_event_friend_connection_status_get_connection_status(event);
tox_node_log(self, "Friend connection status changed to %u", status);
if (status != TOX_CONNECTION_NONE) {
state->alice_connected = true;
}
if (status == TOX_CONNECTION_NONE) {
state->alice_went_offline = true;
}
}
static void bob_script(ToxNode *self, void *ctx)
{
const BobState *state = (const BobState *)ctx;
tox_events_callback_friend_connection_status(tox_node_get_dispatch(self), on_bob_friend_connection_status);
tox_node_wait_for_self_connected(self);
WAIT_UNTIL(state->alice_connected);
tox_node_log(self, "Connected to Alice. Waiting for Alice to delete me...");
WAIT_UNTIL(state->alice_went_offline);
tox_node_log(self, "Alice went offline as expected!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
AliceState alice_state = {false};
BobState bob_state = {false, false};
tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState));
tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState));
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,72 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void alice_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
Tox *tox = tox_node_get_tox(self);
// Test tox_friend_exists
ck_assert(tox_friend_exists(tox, 0));
ck_assert(!tox_friend_exists(tox, 1));
// Test tox_friend_get_public_key
uint8_t pk[TOX_PUBLIC_KEY_SIZE];
Tox_Err_Friend_Get_Public_Key pk_err;
bool pk_success = tox_friend_get_public_key(tox, 0, pk, &pk_err);
ck_assert(pk_success);
ck_assert(pk_err == TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK);
// Test tox_friend_by_public_key
Tox_Err_Friend_By_Public_Key by_pk_err;
uint32_t friend_num = tox_friend_by_public_key(tox, pk, &by_pk_err);
ck_assert(by_pk_err == TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK);
ck_assert(friend_num == 0);
// Test tox_friend_get_last_online
Tox_Err_Friend_Get_Last_Online lo_err;
uint64_t last_online = tox_friend_get_last_online(tox, 0, &lo_err);
ck_assert(lo_err == TOX_ERR_FRIEND_GET_LAST_ONLINE_OK);
// Since they are currently connected, last_online should be recent (non-zero)
ck_assert(last_online > 0);
tox_node_log(self, "Friend query tests passed!");
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
// Bob stays connected while Alice runs her tests
ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0);
WAIT_UNTIL(tox_node_is_finished(alice));
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,83 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
uint32_t message_id;
bool received_receipt;
} AliceState;
static void on_read_receipt(const Tox_Event_Friend_Read_Receipt *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
AliceState *state = (AliceState *)tox_node_get_script_ctx(self);
uint32_t friend_number = tox_event_friend_read_receipt_get_friend_number(event);
uint32_t message_id = tox_event_friend_read_receipt_get_message_id(event);
tox_node_log(self, "Received read receipt for friend %u, message %u", friend_number, message_id);
if (friend_number == 0 && message_id == state->message_id) {
state->received_receipt = true;
}
}
static void alice_script(ToxNode *self, void *ctx)
{
AliceState *state = (AliceState *)ctx;
tox_events_callback_friend_read_receipt(tox_node_get_dispatch(self), on_read_receipt);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
tox_node_log(self, "Sending message to Bob...");
uint8_t msg[] = "Hello Bob!";
Tox_Err_Friend_Send_Message err;
state->message_id = tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err);
ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK);
tox_node_log(self, "Message sent with ID %u, waiting for read receipt...", state->message_id);
WAIT_UNTIL(state->received_receipt);
tox_node_log(self, "Read receipt received successfully!");
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
tox_node_log(self, "Waiting for message from Alice...");
WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE);
tox_node_log(self, "Received message! Tox will automatically send a read receipt.");
// We stay here to allow Alice to receive the receipt
// We wait until alice is finished
ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0);
WAIT_UNTIL(tox_node_is_finished(alice));
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
AliceState alice_state = {0, false};
tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState));
tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,102 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define FR_MESSAGE "Gentoo"
#define FR_TOX_COUNT 33
typedef struct {
uint32_t requests_received;
} ReceiverState;
static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
ReceiverState *state = (ReceiverState *)tox_node_get_script_ctx(self);
const uint8_t *public_key = tox_event_friend_request_get_public_key(event);
const uint8_t *data = tox_event_friend_request_get_message(event);
const size_t length = tox_event_friend_request_get_message_length(event);
if (length == sizeof(FR_MESSAGE) && memcmp(FR_MESSAGE, data, sizeof(FR_MESSAGE)) == 0) {
tox_friend_add_norequest(tox_node_get_tox(self), public_key, nullptr);
state->requests_received++;
tox_node_log(self, "Friend request received: %u", state->requests_received);
}
}
static void receiver_script(ToxNode *self, void *ctx)
{
const ReceiverState *state = (const ReceiverState *)ctx;
tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request);
WAIT_UNTIL(tox_node_is_self_connected(self));
// Wait for all requests to be received and friends connected
WAIT_UNTIL(state->requests_received == FR_TOX_COUNT - 1);
// Also wait until they are all connected as friends
bool all_connected = false;
while (!all_connected && tox_scenario_is_running(self)) {
all_connected = true;
for (uint32_t i = 0; i < FR_TOX_COUNT - 1; ++i) {
if (!tox_node_is_friend_connected(self, i)) {
all_connected = false;
break;
}
}
if (!all_connected) {
tox_scenario_yield(self);
}
}
tox_node_log(self, "All friends connected");
}
static void sender_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
ToxNode *receiver = (ToxNode *)ctx;
uint8_t address[TOX_ADDRESS_SIZE];
tox_node_get_address(receiver, address);
Tox_Err_Friend_Add err;
tox_friend_add(tox_node_get_tox(self), address, (const uint8_t *)FR_MESSAGE, sizeof(FR_MESSAGE), &err);
if (err != TOX_ERR_FRIEND_ADD_OK) {
tox_node_log(self, "Failed to add friend: %u", err);
return;
}
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to receiver");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
ReceiverState receiver_state = {0};
ToxNode *receiver = tox_scenario_add_node(s, "Receiver", receiver_script, &receiver_state, sizeof(ReceiverState));
ToxNode *senders[FR_TOX_COUNT - 1];
for (int i = 0; i < FR_TOX_COUNT - 1; ++i) {
char alias[16];
snprintf(alias, sizeof(alias), "Sender%d", i);
senders[i] = tox_scenario_add_node(s, alias, sender_script, receiver, 0);
tox_node_bootstrap(senders[i], receiver);
tox_node_bootstrap(receiver, senders[i]);
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef FR_MESSAGE

View File

@@ -0,0 +1,78 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
const uint8_t *message;
size_t length;
} RequestData;
static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
Tox *tox = tox_node_get_tox(self);
const RequestData *data = (const RequestData *)tox_node_get_script_ctx(self);
const uint8_t *msg = tox_event_friend_request_get_message(event);
size_t len = tox_event_friend_request_get_message_length(event);
if (len == data->length && memcmp(msg, data->message, len) == 0) {
tox_friend_add_norequest(tox, tox_event_friend_request_get_public_key(event), nullptr);
}
}
static void alice_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
RequestData *data = (RequestData *)ctx;
WAIT_UNTIL(tox_node_is_self_connected(self));
ToxNode *bob = tox_scenario_get_node(tox_node_get_scenario(self), 1);
uint8_t bob_addr[TOX_ADDRESS_SIZE];
tox_node_get_address(bob, bob_addr);
tox_friend_add(tox, bob_addr, data->message, data->length, nullptr);
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
}
static void test_with_message(int argc, char *argv[], const char *label, const uint8_t *message, size_t length)
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
tox_scenario_log(s, "Testing friend request: %s (length %zu)", label, length);
RequestData data = {message, length};
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, &data, sizeof(RequestData));
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &data, sizeof(RequestData));
tox_node_bootstrap(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
exit(1);
}
tox_scenario_free(s);
}
int main(int argc, char *argv[])
{
test_with_message(argc, argv, "Short", (const uint8_t *)"a", 1);
test_with_message(argc, argv, "Medium", (const uint8_t *)"Hello, let\'s be friends!", 24);
uint8_t long_msg[TOX_MAX_FRIEND_REQUEST_LENGTH];
memset(long_msg, 'F', sizeof(long_msg));
test_with_message(argc, argv, "Max length", long_msg, sizeof(long_msg));
return 0;
}

View File

@@ -0,0 +1,231 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define GROUP_NAME "NASA Headquarters"
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
#define TOPIC "Funny topic here"
#define TOPIC_LEN (sizeof(TOPIC) - 1)
#define PEER0_NICK "Lois"
#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1)
#define PEER0_NICK2 "Terry Davis"
#define PEER0_NICK2_LEN (sizeof(PEER0_NICK2) - 1)
#define PEER1_NICK "Bran"
#define PEER1_NICK_LEN (sizeof(PEER1_NICK) - 1)
#define EXIT_MESSAGE "Goodbye world"
#define EXIT_MESSAGE_LEN (sizeof(EXIT_MESSAGE) - 1)
#define PEER_LIMIT 20
typedef struct {
uint32_t group_number;
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
uint32_t peer_joined_count;
uint32_t self_joined_count;
uint32_t peer_exit_count;
bool peer_nick_updated;
bool peer_status_updated;
uint32_t last_peer_id;
bool is_founder;
bool synced;
} GroupState;
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->peer_joined_count++;
state->last_peer_id = tox_event_group_peer_join_get_peer_id(event);
tox_node_log(self, "Peer joined: %u (total %u)", state->last_peer_id, state->peer_joined_count);
}
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->self_joined_count++;
tox_node_log(self, "Self joined (total %u)", state->self_joined_count);
}
static void on_group_peer_exit(const Tox_Event_Group_Peer_Exit *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->peer_exit_count++;
tox_node_log(self, "Peer exited (total %u)", state->peer_exit_count);
if (state->peer_exit_count == 2) {
size_t len = tox_event_group_peer_exit_get_part_message_length(event);
const uint8_t *msg = tox_event_group_peer_exit_get_part_message(event);
ck_assert(len == EXIT_MESSAGE_LEN);
ck_assert(memcmp(msg, EXIT_MESSAGE, len) == 0);
}
}
static void on_group_peer_name(const Tox_Event_Group_Peer_Name *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
size_t len = tox_event_group_peer_name_get_name_length(event);
const uint8_t *name = tox_event_group_peer_name_get_name(event);
if (len == PEER0_NICK2_LEN && memcmp(name, PEER0_NICK2, len) == 0) {
state->peer_nick_updated = true;
}
}
static void on_group_peer_status(const Tox_Event_Group_Peer_Status *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
if (tox_event_group_peer_status_get_status(event) == TOX_USER_STATUS_BUSY) {
state->peer_status_updated = true;
}
}
static void founder_script(ToxNode *self, void *ctx)
{
GroupState *state = (GroupState *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_self_join(dispatch, on_group_self_join);
tox_node_log(self, "Waiting for self connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Connected!");
Tox_Err_Group_New err_new;
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, &err_new);
ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
tox_node_log(self, "Group created: %u", state->group_number);
tox_group_set_peer_limit(tox, state->group_number, PEER_LIMIT, nullptr);
tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC, TOPIC_LEN, nullptr);
tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
// Signal Peer 1 that group is created
tox_scenario_barrier_wait(self);
// Wait for Peer 1 to join
tox_node_log(self, "Waiting for peer to join...");
WAIT_UNTIL(state->peer_joined_count == 1);
tox_node_log(self, "Peer joined!");
// Sync check
WAIT_UNTIL(tox_group_is_connected(tox, state->group_number, nullptr));
// Change name
tox_group_self_set_name(tox, state->group_number, (const uint8_t *)PEER0_NICK2, PEER0_NICK2_LEN, nullptr);
// Change status
tox_group_self_set_status(tox, state->group_number, TOX_USER_STATUS_BUSY, nullptr);
// Disconnect
tox_node_log(self, "Disconnecting...");
tox_group_disconnect(tox, state->group_number, nullptr);
// Wait some time
for (int i = 0; i < 50; ++i) {
tox_scenario_yield(self);
}
// Reconnect
tox_node_log(self, "Reconnecting...");
tox_group_join(tox, state->chat_id, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, nullptr, 0, nullptr);
WAIT_UNTIL(tox_group_is_connected(tox, state->group_number, nullptr));
// Wait for Peer 1 to see us
for (int i = 0; i < 100; ++i) {
tox_scenario_yield(self);
}
// Leave
tox_node_log(self, "Leaving with message...");
tox_group_leave(tox, state->group_number, (const uint8_t *)EXIT_MESSAGE, EXIT_MESSAGE_LEN, nullptr);
}
static void peer1_script(ToxNode *self, void *ctx)
{
const GroupState *state = (const GroupState *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_self_join(dispatch, on_group_self_join);
tox_events_callback_group_peer_name(dispatch, on_group_peer_name);
tox_events_callback_group_peer_status(dispatch, on_group_peer_status);
tox_events_callback_group_peer_exit(dispatch, on_group_peer_exit);
tox_node_log(self, "Waiting for self connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Connected!");
// Wait for Founder to create group and get chat_id
tox_node_log(self, "Waiting for founder to create group...");
tox_scenario_barrier_wait(self);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const GroupState *founder_view = (const GroupState *)tox_node_get_peer_ctx(founder);
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
memcpy(chat_id, founder_view->chat_id, TOX_GROUP_CHAT_ID_SIZE);
tox_node_log(self, "Got chat ID from founder!");
tox_node_log(self, "Joining group...");
tox_group_join(tox, chat_id, (const uint8_t *)PEER1_NICK, PEER1_NICK_LEN, nullptr, 0, nullptr);
WAIT_UNTIL(state->self_joined_count == 1);
WAIT_UNTIL(state->peer_joined_count == 1);
WAIT_UNTIL(tox_group_is_connected(tox, 0, nullptr));
WAIT_UNTIL(state->peer_nick_updated);
WAIT_UNTIL(state->peer_status_updated);
// Founder will disconnect
WAIT_UNTIL(state->peer_exit_count == 1);
// Change status while alone
tox_group_self_set_status(tox, 0, TOX_USER_STATUS_AWAY, nullptr);
// Founder will reconnect
WAIT_UNTIL(state->peer_joined_count == 2);
// Founder will leave with message
WAIT_UNTIL(state->peer_exit_count == 2);
tox_group_leave(tox, 0, nullptr, 0, nullptr);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
GroupState s0 = { .group_number = UINT32_MAX, .is_founder = true };
GroupState s1 = { .group_number = UINT32_MAX, .is_founder = false };
ToxNode *n0 = tox_scenario_add_node(s, "Founder", founder_script, &s0, sizeof(GroupState));
ToxNode *n1 = tox_scenario_add_node(s, "Peer1", peer1_script, &s1, sizeof(GroupState));
tox_node_bootstrap(n1, n0);
// Note: No friend add needed for public groups, they find each other via DHT
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef PEER_LIMIT
#undef GROUP_NAME
#undef GROUP_NAME_LEN
#undef TOPIC
#undef TOPIC_LEN
#undef PEER0_NICK
#undef PEER0_NICK_LEN
#undef PEER1_NICK
#undef PEER1_NICK_LEN

View File

@@ -0,0 +1,303 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define PASSWORD "dadada"
#define PASS_LEN (sizeof(PASSWORD) - 1)
#define WRONG_PASS "dadadada"
#define WRONG_PASS_LEN (sizeof(WRONG_PASS) - 1)
#define PEER_LIMIT 1
typedef struct {
uint32_t group_number;
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
bool peer_limit_fail;
bool password_fail;
bool connected;
uint32_t peer_count;
} State;
static void on_group_join_fail(const Tox_Event_Group_Join_Fail *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
Tox_Group_Join_Fail fail_type = tox_event_group_join_fail_get_fail_type(event);
if (fail_type == TOX_GROUP_JOIN_FAIL_PEER_LIMIT) {
state->peer_limit_fail = true;
} else if (fail_type == TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD) {
state->password_fail = true;
}
}
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->connected = true;
}
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->peer_count++;
}
static void founder_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_self_join(dispatch, on_group_self_join);
tox_node_wait_for_self_connected(self);
Tox_Err_Group_New err_new;
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"test", 4, (const uint8_t *)"test", 4, &err_new);
ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
// Phase 1: Peer 1 joins with no password
tox_scenario_barrier_wait(self); // Barrier 1: Founder created group
WAIT_UNTIL(state->peer_count == 1);
tox_node_log(self, "Peer 1 joined.");
// Phase 2: Set password
tox_group_set_password(tox, state->group_number, (const uint8_t *)PASSWORD, PASS_LEN, nullptr);
tox_scenario_barrier_wait(self); // Barrier 2: Founder set password
// Phase 3: Peer 2 attempts with no password (and fails)
// Phase 4: Peer 3 attempts with wrong password (and fails)
tox_scenario_barrier_wait(self); // Barrier 3: Peers 2 and 3 finished attempts
// Phase 5: Set peer limit
tox_group_set_peer_limit(tox, state->group_number, PEER_LIMIT, nullptr);
tox_scenario_barrier_wait(self); // Barrier 4: Founder set peer limit
// Phase 6: Peer 4 attempts with correct password (and fails due to limit)
tox_scenario_barrier_wait(self); // Barrier 5: Peer 4 finished attempt
// Phase 7: Remove password and increase limit
tox_group_set_password(tox, state->group_number, nullptr, 0, nullptr);
tox_group_set_peer_limit(tox, state->group_number, 100, nullptr);
tox_scenario_barrier_wait(self); // Barrier 6: Founder relaxed restrictions
// Phase 8: Peer 5 joins
WAIT_UNTIL(state->peer_count >= 2);
// Phase 9: Set private state
tox_group_set_privacy_state(tox, state->group_number, TOX_GROUP_PRIVACY_STATE_PRIVATE, nullptr);
tox_scenario_barrier_wait(self); // Barrier 7: Founder made group private
// Phase 10: Peer 6 attempts join (and fails because it's private)
tox_scenario_barrier_wait(self); // Barrier 8: Peer 6 finished wait
// Phase 11: Make group public again
tox_group_set_privacy_state(tox, state->group_number, TOX_GROUP_PRIVACY_STATE_PUBLIC, nullptr);
tox_scenario_barrier_wait(self); // Barrier 9: Founder made group public again
// Final phase: Everyone leaves
WAIT_UNTIL(state->peer_count >= 2); // At least Peer 1 and 5 are here
tox_scenario_barrier_wait(self); // Barrier 10: Ready to leave
tox_group_leave(tox, state->group_number, nullptr, 0, nullptr);
}
static void peer1_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
tox_node_wait_for_self_connected(self);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const State *founder_view = (const State *)tox_node_get_peer_ctx(founder);
tox_scenario_barrier_wait(self); // Barrier 1: Founder created group
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer1", 5, nullptr, 0, nullptr);
tox_scenario_barrier_wait(self); // Barrier 2
tox_scenario_barrier_wait(self); // Barrier 3
tox_scenario_barrier_wait(self); // Barrier 4
tox_scenario_barrier_wait(self); // Barrier 5
tox_scenario_barrier_wait(self); // Barrier 6
tox_scenario_barrier_wait(self); // Barrier 7
tox_scenario_barrier_wait(self); // Barrier 8
tox_scenario_barrier_wait(self); // Barrier 9
tox_scenario_barrier_wait(self); // Barrier 10
tox_group_leave(tox, 0, nullptr, 0, nullptr);
}
static void peer2_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_events_callback_group_join_fail(tox_node_get_dispatch(self), on_group_join_fail);
tox_node_wait_for_self_connected(self);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const State *founder_view = (const State *)tox_node_get_peer_ctx(founder);
tox_scenario_barrier_wait(self); // 1
tox_scenario_barrier_wait(self); // 2: Password set
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer2", 5, nullptr, 0, nullptr);
WAIT_UNTIL(state->password_fail);
tox_node_log(self, "Blocked by password as expected.");
tox_scenario_barrier_wait(self); // 3
tox_scenario_barrier_wait(self); // 4
tox_scenario_barrier_wait(self); // 5
tox_scenario_barrier_wait(self); // 6
tox_scenario_barrier_wait(self); // 7
tox_scenario_barrier_wait(self); // 8
tox_scenario_barrier_wait(self); // 9
tox_scenario_barrier_wait(self); // 10
}
static void peer3_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_events_callback_group_join_fail(tox_node_get_dispatch(self), on_group_join_fail);
tox_node_wait_for_self_connected(self);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const State *founder_view = (const State *)tox_node_get_peer_ctx(founder);
tox_scenario_barrier_wait(self); // 1
tox_scenario_barrier_wait(self); // 2
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer3", 5, (const uint8_t *)WRONG_PASS, WRONG_PASS_LEN, nullptr);
WAIT_UNTIL(state->password_fail);
tox_node_log(self, "Blocked by wrong password as expected.");
tox_scenario_barrier_wait(self); // 3
tox_scenario_barrier_wait(self); // 4
tox_scenario_barrier_wait(self); // 5
tox_scenario_barrier_wait(self); // 6
tox_scenario_barrier_wait(self); // 7
tox_scenario_barrier_wait(self); // 8
tox_scenario_barrier_wait(self); // 9
tox_scenario_barrier_wait(self); // 10
}
static void peer4_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_events_callback_group_join_fail(tox_node_get_dispatch(self), on_group_join_fail);
tox_node_wait_for_self_connected(self);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const State *founder_view = (const State *)tox_node_get_peer_ctx(founder);
tox_scenario_barrier_wait(self); // 1
tox_scenario_barrier_wait(self); // 2
tox_scenario_barrier_wait(self); // 3
tox_scenario_barrier_wait(self); // 4: Peer limit set
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer4", 5, (const uint8_t *)PASSWORD, PASS_LEN, nullptr);
WAIT_UNTIL(state->peer_limit_fail);
tox_node_log(self, "Blocked by peer limit as expected.");
tox_scenario_barrier_wait(self); // 5
tox_scenario_barrier_wait(self); // 6
tox_scenario_barrier_wait(self); // 7
tox_scenario_barrier_wait(self); // 8
tox_scenario_barrier_wait(self); // 9
tox_scenario_barrier_wait(self); // 10
}
static void peer5_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_events_callback_group_self_join(tox_node_get_dispatch(self), on_group_self_join);
tox_node_wait_for_self_connected(self);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const State *founder_view = (const State *)tox_node_get_peer_ctx(founder);
tox_scenario_barrier_wait(self); // 1
tox_scenario_barrier_wait(self); // 2
tox_scenario_barrier_wait(self); // 3
tox_scenario_barrier_wait(self); // 4
tox_scenario_barrier_wait(self); // 5
tox_scenario_barrier_wait(self); // 6: Restrictions relaxed
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer5", 5, nullptr, 0, nullptr);
WAIT_UNTIL(state->connected);
tox_node_log(self, "Joined group.");
tox_scenario_barrier_wait(self); // 7
tox_scenario_barrier_wait(self); // 8
tox_scenario_barrier_wait(self); // 9
tox_scenario_barrier_wait(self); // 10
tox_group_leave(tox, 0, nullptr, 0, nullptr);
}
static void peer6_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_events_callback_group_self_join(tox_node_get_dispatch(self), on_group_self_join);
tox_node_wait_for_self_connected(self);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const State *founder_view = (const State *)tox_node_get_peer_ctx(founder);
tox_scenario_barrier_wait(self); // 1
tox_scenario_barrier_wait(self); // 2
tox_scenario_barrier_wait(self); // 3
tox_scenario_barrier_wait(self); // 4
tox_scenario_barrier_wait(self); // 5
tox_scenario_barrier_wait(self); // 6
tox_scenario_barrier_wait(self); // 7: Private group
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer6", 5, nullptr, 0, nullptr);
// Wait some time to be sure we are NOT connected
for (int i = 0; i < 2000 / TOX_SCENARIO_TICK_MS; ++i) {
ck_assert(!state->connected);
tox_scenario_yield(self);
}
tox_node_log(self, "Could not join private group via chat ID as expected.");
tox_scenario_barrier_wait(self); // 8
tox_scenario_barrier_wait(self); // 9
tox_scenario_barrier_wait(self); // 10
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
State states[7] = {0};
ToxNode *nodes[7];
nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(State));
nodes[1] = tox_scenario_add_node(s, "Peer1", peer1_script, &states[1], sizeof(State));
nodes[2] = tox_scenario_add_node(s, "Peer2", peer2_script, &states[2], sizeof(State));
nodes[3] = tox_scenario_add_node(s, "Peer3", peer3_script, &states[3], sizeof(State));
nodes[4] = tox_scenario_add_node(s, "Peer4", peer4_script, &states[4], sizeof(State));
nodes[5] = tox_scenario_add_node(s, "Peer5", peer5_script, &states[5], sizeof(State));
nodes[6] = tox_scenario_add_node(s, "Peer6", peer6_script, &states[6], sizeof(State));
for (int i = 1; i < 7; ++i) {
tox_node_bootstrap(nodes[i], nodes[0]);
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef PASSWORD
#undef PASS_LEN
#undef PEER_LIMIT

View File

@@ -0,0 +1,214 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define PEER0_NICK "Thomas"
#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1)
#define PEER1_NICK "Winslow"
#define PEER1_NICK_LEN (sizeof(PEER1_NICK) - 1)
#define TEST_GROUP_NAME "Utah Data Center"
#define TEST_GROUP_NAME_LEN (sizeof(TEST_GROUP_NAME) - 1)
#define TEST_MESSAGE "Where is it I've read that someone condemned to death says or thinks..."
#define TEST_MESSAGE_LEN (sizeof(TEST_MESSAGE) - 1)
#define TEST_PRIVATE_MESSAGE "Don't spill yer beans"
#define TEST_PRIVATE_MESSAGE_LEN (sizeof(TEST_PRIVATE_MESSAGE) - 1)
#define TEST_CUSTOM_PACKET "Why'd ya spill yer beans?"
#define TEST_CUSTOM_PACKET_LEN (sizeof(TEST_CUSTOM_PACKET) - 1)
#define TEST_CUSTOM_PRIVATE_PACKET "This is a custom private packet. Enjoy."
#define TEST_CUSTOM_PRIVATE_PACKET_LEN (sizeof(TEST_CUSTOM_PRIVATE_PACKET) - 1)
#define MAX_NUM_MESSAGES_LOSSLESS_TEST 100
typedef struct {
uint32_t group_number;
uint32_t peer_id;
bool peer_joined;
bool message_received;
bool private_message_received;
size_t custom_packets_received;
size_t custom_private_packets_received;
int32_t last_msg_recv;
bool lossless_done;
} GroupState;
static uint16_t get_message_checksum(const uint8_t *message, uint16_t length)
{
uint16_t sum = 0;
for (size_t i = 0; i < length; ++i) {
sum += message[i];
}
return sum;
}
static void on_group_invite(const Tox_Event_Group_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
Tox_Err_Group_Invite_Accept err;
state->group_number = tox_group_invite_accept(tox_node_get_tox(self),
tox_event_group_invite_get_friend_number(event),
tox_event_group_invite_get_invite_data(event),
tox_event_group_invite_get_invite_data_length(event),
(const uint8_t *)PEER1_NICK, PEER1_NICK_LEN, nullptr, 0, &err);
tox_node_log(self, "Accepted group invite, group %u", state->group_number);
}
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->peer_joined = true;
state->peer_id = tox_event_group_peer_join_get_peer_id(event);
tox_node_log(self, "Peer %u joined group", state->peer_id);
}
static void on_group_message(const Tox_Event_Group_Message *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
const uint8_t *msg = tox_event_group_message_get_message(event);
size_t len = tox_event_group_message_get_message_length(event);
if (len == TEST_MESSAGE_LEN && memcmp(msg, TEST_MESSAGE, len) == 0) {
state->message_received = true;
tox_node_log(self, "Received group message");
} else if (len >= 4) {
uint16_t start, checksum;
memcpy(&start, msg, 2);
memcpy(&checksum, msg + 2, 2);
if (checksum == get_message_checksum(msg + 4, len - 4)) {
state->last_msg_recv = start;
if (start == MAX_NUM_MESSAGES_LOSSLESS_TEST) {
state->lossless_done = true;
}
}
}
}
static void on_group_private_message(const Tox_Event_Group_Private_Message *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
const uint8_t *msg = tox_event_group_private_message_get_message(event);
size_t len = tox_event_group_private_message_get_message_length(event);
if (len == TEST_PRIVATE_MESSAGE_LEN && memcmp(msg, TEST_PRIVATE_MESSAGE, len) == 0) {
state->private_message_received = true;
tox_node_log(self, "Received private group message");
}
}
static void on_group_custom_packet(const Tox_Event_Group_Custom_Packet *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->custom_packets_received++;
tox_node_log(self, "Received custom packet %zu", state->custom_packets_received);
}
static void on_group_custom_private_packet(const Tox_Event_Group_Custom_Private_Packet *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->custom_private_packets_received++;
tox_node_log(self, "Received custom private packet %zu", state->custom_private_packets_received);
}
static void peer0_script(ToxNode *self, void *ctx)
{
GroupState *state = (GroupState *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_message(dispatch, on_group_message);
tox_events_callback_group_private_message(dispatch, on_group_private_message);
tox_events_callback_group_custom_packet(dispatch, on_group_custom_packet);
tox_events_callback_group_custom_private_packet(dispatch, on_group_custom_private_packet);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PRIVATE, (const uint8_t *)TEST_GROUP_NAME, TEST_GROUP_NAME_LEN, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, nullptr);
tox_group_invite_friend(tox, state->group_number, 0, nullptr);
tox_node_log(self, "Created group and invited friend");
WAIT_UNTIL(state->peer_joined);
WAIT_UNTIL(state->message_received);
WAIT_UNTIL(state->private_message_received);
WAIT_UNTIL(state->custom_packets_received >= 2);
WAIT_UNTIL(state->custom_private_packets_received >= 2);
// Lossless test
uint8_t m[TOX_GROUP_MAX_MESSAGE_LENGTH];
for (uint16_t i = 0; i <= MAX_NUM_MESSAGES_LOSSLESS_TEST; ++i) {
uint32_t size = 4 + (rand() % 100);
memcpy(m, &i, 2);
for (size_t j = 4; j < size; ++j) {
uint32_t val = rand() % 256;
m[j] = (uint8_t)val;
}
uint16_t checksum = get_message_checksum(m + 4, (uint16_t)(size - 4));
memcpy(m + 2, &checksum, 2);
tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, m, size, nullptr);
if (i % 10 == 0) {
tox_scenario_yield(self);
}
}
tox_node_log(self, "Done");
}
static void peer1_script(ToxNode *self, void *ctx)
{
const GroupState *state = (const GroupState *)ctx;
const Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_invite(dispatch, on_group_invite);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_message(dispatch, on_group_message);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(state->peer_joined);
tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)TEST_MESSAGE, TEST_MESSAGE_LEN, nullptr);
tox_group_send_private_message(tox, state->group_number, state->peer_id, TOX_MESSAGE_TYPE_ACTION, (const uint8_t *)TEST_PRIVATE_MESSAGE, TEST_PRIVATE_MESSAGE_LEN, nullptr);
tox_group_send_custom_packet(tox, state->group_number, true, (const uint8_t *)TEST_CUSTOM_PACKET, TEST_CUSTOM_PACKET_LEN, nullptr);
tox_group_send_custom_packet(tox, state->group_number, false, (const uint8_t *)TEST_CUSTOM_PACKET, TEST_CUSTOM_PACKET_LEN, nullptr);
tox_group_send_custom_private_packet(tox, state->group_number, state->peer_id, true, (const uint8_t *)TEST_CUSTOM_PRIVATE_PACKET, TEST_CUSTOM_PRIVATE_PACKET_LEN, nullptr);
tox_group_send_custom_private_packet(tox, state->group_number, state->peer_id, false, (const uint8_t *)TEST_CUSTOM_PRIVATE_PACKET, TEST_CUSTOM_PRIVATE_PACKET_LEN, nullptr);
WAIT_UNTIL(state->lossless_done);
tox_node_log(self, "Done");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
GroupState s0 = { .last_msg_recv = -1 }, s1 = { .last_msg_recv = -1 };
ToxNode *n0 = tox_scenario_add_node(s, "Peer0", peer0_script, &s0, sizeof(GroupState));
ToxNode *n1 = tox_scenario_add_node(s, "Peer1", peer1_script, &s1, sizeof(GroupState));
tox_node_friend_add(n0, n1);
tox_node_friend_add(n1, n0);
tox_node_bootstrap(n0, n1);
tox_node_bootstrap(n1, n0);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef PEER0_NICK
#undef PEER0_NICK_LEN
#undef PEER1_NICK
#undef PEER1_NICK_LEN

View File

@@ -0,0 +1,367 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_PEERS 5
#define GROUP_NAME "Moderation Test Group"
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
typedef struct {
uint32_t group_number;
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
bool chat_id_ready;
uint32_t peer_count;
bool connected;
Tox_Group_Role self_role;
Tox_Group_Voice_State voice_state;
bool kicked;
uint32_t peer_ids[NUM_PEERS]; // Map node index to group peer_id
Tox_Group_Mod_Event last_mod_event;
uint32_t last_mod_target;
} ModState;
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
ModState *state = (ModState *)tox_node_get_script_ctx(self);
state->connected = true;
uint32_t group_number = tox_event_group_self_join_get_group_number(event);
uint32_t self_id = tox_group_self_get_peer_id(tox_node_get_tox(self), group_number, nullptr);
state->self_role = tox_group_self_get_role(tox_node_get_tox(self), group_number, nullptr);
tox_node_log(self, "Joined group %u (Peer ID: %u) with role %s", group_number, self_id, tox_group_role_to_string(state->self_role));
}
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
ModState *state = (ModState *)tox_node_get_script_ctx(self);
uint32_t group_number = tox_event_group_peer_join_get_group_number(event);
uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event);
tox_node_log(self, "Peer %u joined the group", peer_id);
state->peer_count++;
Tox_Err_Group_Peer_Query q_err;
size_t length = tox_group_peer_get_name_size(tox_node_get_tox(self), group_number, peer_id, &q_err);
if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK && length > 0) {
uint8_t name[TOX_MAX_NAME_LENGTH];
tox_group_peer_get_name(tox_node_get_tox(self), group_number, peer_id, name, &q_err);
if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK) {
tox_node_log(self, "Peer %u name identified: %.*s", peer_id, (int)length, name);
if (length == 7 && memcmp(name, "Founder", 7) == 0) {
state->peer_ids[0] = peer_id;
} else if (length >= 5 && memcmp(name, "Peer", 4) == 0) {
int idx = atoi((const char *)name + 4);
if (idx > 0 && idx < NUM_PEERS) {
state->peer_ids[idx] = peer_id;
}
}
}
}
}
static void on_group_moderation(const Tox_Event_Group_Moderation *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
ModState *state = (ModState *)tox_node_get_script_ctx(self);
state->last_mod_event = tox_event_group_moderation_get_mod_type(event);
state->last_mod_target = tox_event_group_moderation_get_target_peer_id(event);
Tox_Err_Group_Self_Query err;
state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, &err);
if (state->last_mod_event == TOX_GROUP_MOD_EVENT_KICK && state->last_mod_target == tox_group_self_get_peer_id(tox_node_get_tox(self), state->group_number, nullptr)) {
state->kicked = true;
}
tox_node_log(self, "Moderation event: %s on peer %u. My role is now %s",
tox_group_mod_event_to_string(state->last_mod_event),
state->last_mod_target,
tox_group_role_to_string(state->self_role));
}
static void on_group_voice_state(const Tox_Event_Group_Voice_State *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
ModState *state = (ModState *)tox_node_get_script_ctx(self);
state->voice_state = tox_event_group_voice_state_get_voice_state(event);
tox_node_log(self, "Voice state updated: %u", state->voice_state);
}
static void common_init(ToxNode *self, ModState *state)
{
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_self_join(dispatch, on_group_self_join);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_moderation(dispatch, on_group_moderation);
tox_events_callback_group_voice_state(dispatch, on_group_voice_state);
for (int i = 0; i < NUM_PEERS; ++i) {
state->peer_ids[i] = UINT32_MAX;
}
tox_node_log(self, "Waiting for self connection...");
tox_node_wait_for_self_connected(self);
tox_node_log(self, "Connected!");
}
static void wait_for_peer_role(ToxNode *self, uint32_t peer_idx, Tox_Group_Role expected_role)
{
const ToxNode *peer = tox_scenario_get_node(tox_node_get_scenario(self), peer_idx);
const ModState *peer_view = (const ModState *)tox_node_get_peer_ctx(peer);
tox_node_log(self, "Waiting for Peer %u to have role %s", peer_idx, tox_group_role_to_string(expected_role));
WAIT_UNTIL(peer_view->self_role == expected_role);
tox_node_log(self, "Peer %u now has role %s", peer_idx, tox_group_role_to_string(expected_role));
}
static void founder_script(ToxNode *self, void *ctx)
{
ModState *state = (ModState *)ctx;
Tox *tox = tox_node_get_tox(self);
common_init(self, state);
Tox_Err_Group_New err_new;
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Founder", 7, &err_new);
ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
state->self_role = TOX_GROUP_ROLE_FOUNDER;
state->peer_ids[0] = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
state->chat_id_ready = true;
// Barrier 1: Wait for all peers to join and be seen by everyone
tox_scenario_barrier_wait(self);
WAIT_UNTIL(state->peer_count == NUM_PEERS - 1);
tox_node_log(self, "All peers joined");
// Wait until we know all peer IDs
for (int i = 1; i < NUM_PEERS; ++i) {
WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX);
}
tox_node_log(self, "All peer IDs identified");
tox_scenario_barrier_wait(self); // Sync point after everyone sees everyone
// Barrier 2: Peer 1 becomes Moderator
tox_group_set_role(tox, state->group_number, state->peer_ids[1], TOX_GROUP_ROLE_MODERATOR, nullptr);
for (int i = 0; i < NUM_PEERS; ++i) {
wait_for_peer_role(self, 1, TOX_GROUP_ROLE_MODERATOR);
}
tox_scenario_barrier_wait(self);
// Barrier 3: Peer 2 and 3 become Observer
tox_group_set_role(tox, state->group_number, state->peer_ids[2], TOX_GROUP_ROLE_OBSERVER, nullptr);
tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_OBSERVER, nullptr);
for (int i = 0; i < NUM_PEERS; ++i) {
wait_for_peer_role(self, 2, TOX_GROUP_ROLE_OBSERVER);
wait_for_peer_role(self, 3, TOX_GROUP_ROLE_OBSERVER);
}
tox_scenario_barrier_wait(self);
// Barrier 4: Voice State tests
tox_node_log(self, "Setting voice state to MODERATOR");
tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_MODERATOR, nullptr);
tox_scenario_barrier_wait(self); // Phase 1 set
tox_scenario_barrier_wait(self); // Phase 1 done
tox_node_log(self, "Setting voice state to FOUNDER");
tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_FOUNDER, nullptr);
tox_scenario_barrier_wait(self); // Phase 2 set
tox_scenario_barrier_wait(self); // Phase 2 done
tox_node_log(self, "Setting voice state to ALL");
tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_ALL, nullptr);
tox_scenario_barrier_wait(self); // Phase 3 set
tox_scenario_barrier_wait(self); // Phase 3 done
// Barrier 5: Peer 1 (Mod) promotes Peer 2 back to User
tox_scenario_barrier_wait(self);
wait_for_peer_role(self, 2, TOX_GROUP_ROLE_USER);
// Barrier 6: Founder promotes Peer 3 to Moderator
tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_MODERATOR, nullptr);
wait_for_peer_role(self, 3, TOX_GROUP_ROLE_MODERATOR);
tox_scenario_barrier_wait(self);
// Barrier 7: Moderator (Peer 1) attempts to kick/demote Founder (should fail)
tox_scenario_barrier_wait(self);
// Barrier 8: Founder kicks Moderator (Peer 1)
tox_group_kick_peer(tox, state->group_number, state->peer_ids[1], nullptr);
tox_scenario_barrier_wait(self);
// Barrier 9: Founder demotes Moderator (Peer 3) to User
tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_USER, nullptr);
wait_for_peer_role(self, 3, TOX_GROUP_ROLE_USER);
tox_scenario_barrier_wait(self);
tox_scenario_barrier_wait(self); // Done
}
static void peer_script(ToxNode *self, void *ctx)
{
ModState *state = (ModState *)ctx;
Tox *tox = tox_node_get_tox(self);
common_init(self, state);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const ModState *founder_view = (const ModState *)tox_node_get_peer_ctx(founder);
while (!founder_view->chat_id_ready) {
tox_scenario_yield(self);
}
tox_node_log(self, "Got chat ID from founder");
char name[16];
snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self));
Tox_Err_Group_Join err_join;
state->group_number = tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, &err_join);
if (state->group_number == UINT32_MAX) {
tox_node_log(self, "tox_group_join failed with error %u", err_join);
}
ck_assert(state->group_number != UINT32_MAX);
WAIT_UNTIL(state->connected);
uint32_t self_id = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
state->peer_ids[tox_node_get_index(self)] = self_id;
tox_scenario_barrier_wait(self); // Barrier 1: Joined
// Wait until we know all peer IDs
for (uint32_t i = 0; i < NUM_PEERS; ++i) {
if (tox_node_get_index(self) != i) {
WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX);
}
}
tox_scenario_barrier_wait(self); // Sync point after everyone sees everyone
tox_scenario_barrier_wait(self); // Barrier 2: Peer 1 Moderator
wait_for_peer_role(self, 1, TOX_GROUP_ROLE_MODERATOR);
tox_scenario_barrier_wait(self); // Barrier 3: Peer 2/3 Observer
wait_for_peer_role(self, 2, TOX_GROUP_ROLE_OBSERVER);
wait_for_peer_role(self, 3, TOX_GROUP_ROLE_OBSERVER);
// Barrier 4: Voice State tests
// Sub-phase 1: MODERATOR
tox_scenario_barrier_wait(self); // Phase 1 set
WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_MODERATOR);
Tox_Err_Group_Send_Message err_msg;
tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg);
if (state->self_role == TOX_GROUP_ROLE_MODERATOR || state->self_role == TOX_GROUP_ROLE_FOUNDER) {
if (err_msg != TOX_ERR_GROUP_SEND_MESSAGE_OK) {
tox_node_log(self, "Expected OK, got %u. Role: %s", err_msg, tox_group_role_to_string(state->self_role));
}
ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK);
} else {
if (err_msg != TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS) {
tox_node_log(self, "Expected PERMISSIONS, got %u. Role: %s", err_msg, tox_group_role_to_string(state->self_role));
}
ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS);
}
tox_scenario_barrier_wait(self); // Phase 1 done
// Sub-phase 2: FOUNDER
tox_scenario_barrier_wait(self); // Phase 2 set
WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_FOUNDER);
tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg);
if (state->self_role == TOX_GROUP_ROLE_FOUNDER) {
ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK);
} else {
ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS);
}
tox_scenario_barrier_wait(self); // Phase 2 done
// Sub-phase 3: ALL
tox_scenario_barrier_wait(self); // Phase 3 set
WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_ALL);
tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg);
if (state->self_role == TOX_GROUP_ROLE_OBSERVER) {
ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS);
} else {
ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK);
}
tox_scenario_barrier_wait(self); // Phase 3 done
// Barrier 5: Peer 1 (Mod) promotes Peer 2 back to User
if (tox_node_get_index(self) == 1) { // Peer 1
uint32_t peer2_id = state->peer_ids[2];
tox_group_set_role(tox, state->group_number, peer2_id, TOX_GROUP_ROLE_USER, nullptr);
}
tox_scenario_barrier_wait(self);
wait_for_peer_role(self, 2, TOX_GROUP_ROLE_USER);
// Barrier 6: Founder promotes Peer 3 to Moderator
tox_scenario_barrier_wait(self);
wait_for_peer_role(self, 3, TOX_GROUP_ROLE_MODERATOR);
// Barrier 7: Moderator (Peer 1) attempts to kick/demote Founder (should fail)
if (tox_node_get_index(self) == 1) {
Tox_Err_Group_Kick_Peer err_kick;
uint32_t founder_peer_id = state->peer_ids[0];
tox_group_kick_peer(tox, state->group_number, founder_peer_id, &err_kick);
ck_assert(err_kick != TOX_ERR_GROUP_KICK_PEER_OK);
Tox_Err_Group_Set_Role err_role;
tox_group_set_role(tox, state->group_number, founder_peer_id, TOX_GROUP_ROLE_USER, &err_role);
ck_assert(err_role != TOX_ERR_GROUP_SET_ROLE_OK);
}
tox_scenario_barrier_wait(self);
// Barrier 8: Founder kicks Moderator (Peer 1)
tox_scenario_barrier_wait(self);
if (tox_node_get_index(self) == 1) {
WAIT_UNTIL(state->kicked);
return; // Exit script
}
// Barrier 9: Founder demotes Moderator (Peer 3) to User
tox_scenario_barrier_wait(self);
wait_for_peer_role(self, 3, TOX_GROUP_ROLE_USER);
tox_scenario_barrier_wait(self); // Done
}
int main(int argc, char *argv[])
{
setvbuf(stdout, nullptr, _IONBF, 0);
setvbuf(stderr, nullptr, _IONBF, 0);
ToxScenario *s = tox_scenario_new(argc, argv, 300000); // 5 virtual minutes
ModState states[NUM_PEERS] = {0};
ToxNode *nodes[NUM_PEERS];
nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(ModState));
static char aliases[NUM_PEERS][16];
for (int i = 1; i < NUM_PEERS; ++i) {
snprintf(aliases[i], sizeof(aliases[i]), "Peer%d", i);
nodes[i] = tox_scenario_add_node(s, aliases[i], peer_script, &states[i], sizeof(ModState));
}
for (int i = 0; i < NUM_PEERS; ++i) {
for (int j = 0; j < NUM_PEERS; ++j) {
if (i != j) {
tox_node_bootstrap(nodes[i], nodes[j]);
tox_node_friend_add(nodes[i], nodes[j]);
}
}
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef GROUP_NAME
#undef GROUP_NAME_LEN
#undef NUM_PEERS

View File

@@ -0,0 +1,172 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define GROUP_NAME "The Test Chamber"
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
#define TOPIC "They're waiting for you Gordon..."
#define TOPIC_LEN (sizeof(TOPIC) - 1)
#define NEW_PRIV_STATE TOX_GROUP_PRIVACY_STATE_PRIVATE
#define PASSWORD "password123"
#define PASS_LEN (sizeof(PASSWORD) - 1)
#define PEER_LIMIT 69
#define PEER0_NICK "Mike"
#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1)
#define NEW_USER_STATUS TOX_USER_STATUS_BUSY
typedef struct {
bool peer_joined;
} State;
static void on_group_invite(const Tox_Event_Group_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
uint32_t friend_number = tox_event_group_invite_get_friend_number(event);
const uint8_t *invite_data = tox_event_group_invite_get_invite_data(event);
size_t length = tox_event_group_invite_get_invite_data_length(event);
tox_group_invite_accept(tox_node_get_tox(self), friend_number, invite_data, length, (const uint8_t *)"Bob", 3, nullptr, 0, nullptr);
}
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->peer_joined = true;
}
static void check_founder_state(ToxNode *self, uint32_t group_number, const uint8_t *expected_chat_id, const uint8_t *expected_self_pk)
{
Tox *tox = tox_node_get_tox(self);
Tox_Err_Group_State_Query query_err;
// Group state
ck_assert(tox_group_get_privacy_state(tox, group_number, &query_err) == NEW_PRIV_STATE);
ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK);
uint8_t password[TOX_GROUP_MAX_PASSWORD_SIZE];
size_t pass_len = tox_group_get_password_size(tox, group_number, &query_err);
ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK);
tox_group_get_password(tox, group_number, password, &query_err);
ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK);
ck_assert(pass_len == PASS_LEN && memcmp(password, PASSWORD, pass_len) == 0);
uint8_t gname[TOX_GROUP_MAX_GROUP_NAME_LENGTH];
size_t gname_len = tox_group_get_name_size(tox, group_number, &query_err);
ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK);
tox_group_get_name(tox, group_number, gname, &query_err);
ck_assert(gname_len == GROUP_NAME_LEN && memcmp(gname, GROUP_NAME, gname_len) == 0);
ck_assert(tox_group_get_peer_limit(tox, group_number, nullptr) == PEER_LIMIT);
ck_assert(tox_group_get_topic_lock(tox, group_number, nullptr) == TOX_GROUP_TOPIC_LOCK_DISABLED);
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
tox_group_get_chat_id(tox, group_number, chat_id, nullptr);
ck_assert(memcmp(chat_id, expected_chat_id, TOX_GROUP_CHAT_ID_SIZE) == 0);
// Self state
Tox_Err_Group_Self_Query sq_err;
uint8_t self_name[TOX_MAX_NAME_LENGTH];
size_t self_len = tox_group_self_get_name_size(tox, group_number, &sq_err);
tox_group_self_get_name(tox, group_number, self_name, nullptr);
ck_assert(self_len == PEER0_NICK_LEN && memcmp(self_name, PEER0_NICK, self_len) == 0);
ck_assert(tox_group_self_get_status(tox, group_number, nullptr) == NEW_USER_STATUS);
ck_assert(tox_group_self_get_role(tox, group_number, nullptr) == TOX_GROUP_ROLE_FOUNDER);
uint8_t self_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE];
tox_group_self_get_public_key(tox, group_number, self_pk, nullptr);
ck_assert(memcmp(self_pk, expected_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0);
}
static void founder_script(ToxNode *self, void *ctx)
{
const State *state = (const State *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_events_callback_group_peer_join(tox_node_get_dispatch(self), on_group_peer_join);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
Tox_Err_Group_New err_new;
uint32_t group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PRIVATE, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"test", 4, &err_new);
ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
tox_group_get_chat_id(tox, group_number, chat_id, nullptr);
uint8_t founder_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE];
tox_group_self_get_public_key(tox, group_number, founder_pk, nullptr);
tox_group_invite_friend(tox, group_number, 0, nullptr);
WAIT_UNTIL(state->peer_joined);
tox_node_log(self, "Bob joined. Changing group state...");
tox_group_set_topic(tox, group_number, (const uint8_t *)TOPIC, TOPIC_LEN, nullptr);
tox_group_set_topic_lock(tox, group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr);
tox_group_set_privacy_state(tox, group_number, NEW_PRIV_STATE, nullptr);
tox_group_set_password(tox, group_number, (const uint8_t *)PASSWORD, PASS_LEN, nullptr);
tox_group_set_peer_limit(tox, group_number, PEER_LIMIT, nullptr);
tox_group_self_set_name(tox, group_number, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, nullptr);
tox_group_self_set_status(tox, group_number, NEW_USER_STATUS, nullptr);
tox_scenario_yield(self);
tox_node_log(self, "Saving and reloading...");
tox_node_reload(self);
tox_node_log(self, "Reloaded.");
tox_node_wait_for_self_connected(self);
check_founder_state(self, group_number, chat_id, founder_pk);
tox_node_log(self, "State verified after reload.");
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_events_callback_group_invite(tox_node_get_dispatch(self), on_group_invite);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
tox_node_log(self, "Waiting for founder to finish...");
WAIT_UNTIL(tox_node_is_finished(founder));
tox_node_log(self, "Founder finished, Bob exiting.");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
State states[2] = {{0}};
Tox_Options *opts = tox_options_new(nullptr);
tox_options_set_experimental_groups_persistence(opts, true);
tox_options_set_ipv6_enabled(opts, false);
tox_options_set_local_discovery_enabled(opts, false);
ToxNode *alice = tox_scenario_add_node_ex(s, "Alice", founder_script, &states[0], sizeof(State), opts);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &states[1], sizeof(State));
tox_options_free(opts);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef PASSWORD
#undef PASS_LEN
#undef PEER_LIMIT
#undef GROUP_NAME
#undef GROUP_NAME_LEN
#undef TOPIC
#undef TOPIC_LEN
#undef PEER0_NICK
#undef PEER0_NICK_LEN

View File

@@ -0,0 +1,186 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NUM_PEERS 5
#define GROUP_NAME "The Crystal Palace"
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
#define PASSWORD "dadada"
#define PASS_LEN (sizeof(PASSWORD) - 1)
#define PEER_LIMIT_1 NUM_PEERS
#define PEER_LIMIT_2 50
typedef struct {
uint32_t group_number;
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
uint32_t peer_count;
Tox_Group_Privacy_State privacy_state;
Tox_Group_Voice_State voice_state;
Tox_Group_Topic_Lock topic_lock;
uint32_t peer_limit;
uint8_t password[TOX_GROUP_MAX_PASSWORD_SIZE];
size_t password_len;
} GroupState;
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->peer_count++;
}
static void on_group_privacy_state(const Tox_Event_Group_Privacy_State *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->privacy_state = tox_event_group_privacy_state_get_privacy_state(event);
}
static void on_group_voice_state(const Tox_Event_Group_Voice_State *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->voice_state = tox_event_group_voice_state_get_voice_state(event);
}
static void on_group_topic_lock(const Tox_Event_Group_Topic_Lock *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->topic_lock = tox_event_group_topic_lock_get_topic_lock(event);
}
static void on_group_peer_limit(const Tox_Event_Group_Peer_Limit *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->peer_limit = tox_event_group_peer_limit_get_peer_limit(event);
}
static void on_group_password(const Tox_Event_Group_Password *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
GroupState *state = (GroupState *)tox_node_get_script_ctx(self);
state->password_len = tox_event_group_password_get_password_length(event);
if (state->password_len > 0) {
memcpy(state->password, tox_event_group_password_get_password(event), state->password_len);
}
}
static void founder_script(ToxNode *self, void *ctx)
{
GroupState *state = (GroupState *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_node_wait_for_self_connected(self);
Tox_Err_Group_New err_new;
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN,
(const uint8_t *)"Founder", 7, &err_new);
ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
tox_group_set_peer_limit(tox, state->group_number, PEER_LIMIT_1, nullptr);
tox_group_set_privacy_state(tox, state->group_number, TOX_GROUP_PRIVACY_STATE_PUBLIC, nullptr);
tox_group_set_password(tox, state->group_number, (const uint8_t *)PASSWORD, PASS_LEN, nullptr);
tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_ENABLED, nullptr);
tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_ALL, nullptr);
tox_scenario_barrier_wait(self); // Barrier 1: Founder set initial state
WAIT_UNTIL(state->peer_count == NUM_PEERS - 1);
tox_node_log(self, "All peers joined.");
tox_scenario_barrier_wait(self); // Barrier 2: All peers joined
tox_node_log(self, "Changing group state...");
tox_group_set_peer_limit(tox, state->group_number, PEER_LIMIT_2, nullptr);
tox_group_set_privacy_state(tox, state->group_number, TOX_GROUP_PRIVACY_STATE_PRIVATE, nullptr);
tox_group_set_password(tox, state->group_number, nullptr, 0, nullptr);
tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr);
tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_MODERATOR, nullptr);
tox_scenario_barrier_wait(self); // Barrier 3: State changed
tox_scenario_barrier_wait(self); // Barrier 4: All peers verified state
}
static void peer_script(ToxNode *self, void *ctx)
{
const GroupState *state = (const GroupState *)ctx;
Tox *tox = tox_node_get_tox(self);
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_privacy_state(dispatch, on_group_privacy_state);
tox_events_callback_group_voice_state(dispatch, on_group_voice_state);
tox_events_callback_group_topic_lock(dispatch, on_group_topic_lock);
tox_events_callback_group_peer_limit(dispatch, on_group_peer_limit);
tox_events_callback_group_password(dispatch, on_group_password);
tox_node_wait_for_self_connected(self);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const GroupState *founder_view = (const GroupState *)tox_node_get_peer_ctx(founder);
tox_scenario_barrier_wait(self); // Barrier 1: Founder set initial state
Tox_Err_Group_Join join_err;
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer", 4, (const uint8_t *)PASSWORD, PASS_LEN, &join_err);
if (join_err != TOX_ERR_GROUP_JOIN_OK) {
tox_node_log(self, "tox_group_join failed with error: %s", tox_err_group_join_to_string(join_err));
}
ck_assert(join_err == TOX_ERR_GROUP_JOIN_OK);
WAIT_UNTIL(tox_group_is_connected(tox, 0, nullptr));
tox_scenario_barrier_wait(self); // Barrier 2: All peers joined
tox_scenario_barrier_wait(self); // Barrier 3: State changed
WAIT_UNTIL(state->privacy_state == TOX_GROUP_PRIVACY_STATE_PRIVATE);
WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_MODERATOR);
WAIT_UNTIL(state->topic_lock == TOX_GROUP_TOPIC_LOCK_DISABLED);
WAIT_UNTIL(state->peer_limit == PEER_LIMIT_2);
WAIT_UNTIL(state->password_len == 0);
tox_node_log(self, "State verified.");
tox_scenario_barrier_wait(self); // Barrier 4: All peers verified state
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
GroupState states[NUM_PEERS] = {{0}};
ToxNode *nodes[NUM_PEERS];
nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(GroupState));
for (int i = 1; i < NUM_PEERS; ++i) {
char alias[16];
snprintf(alias, sizeof(alias), "Peer%d", i);
nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(GroupState));
}
for (int i = 1; i < NUM_PEERS; ++i) {
tox_node_bootstrap(nodes[i], nodes[0]);
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef PASSWORD
#undef PASS_LEN
#undef GROUP_NAME
#undef GROUP_NAME_LEN
#undef NUM_PEERS

View File

@@ -0,0 +1,263 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_PEERS 5
#define GROUP_NAME "Sync Test Group"
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
typedef struct {
uint32_t group_number;
uint32_t peer_count;
bool connected;
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH];
size_t topic_len;
uint32_t peers[NUM_PEERS]; // Track peer IDs we've seen
Tox_Group_Role self_role;
} SyncState;
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
SyncState *state = (SyncState *)tox_node_get_script_ctx(self);
state->connected = true;
tox_node_log(self, "Joined group");
}
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
SyncState *state = (SyncState *)tox_node_get_script_ctx(self);
uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event);
// Check if it's ourselves
Tox_Err_Group_Self_Query err_peer;
uint32_t self_id = tox_group_self_get_peer_id(tox_node_get_tox(self), state->group_number, &err_peer);
if (err_peer == TOX_ERR_GROUP_SELF_QUERY_OK && peer_id == self_id) {
return;
}
for (uint32_t i = 0; i < state->peer_count; ++i) {
if (state->peers[i] == peer_id) {
return;
}
}
if (state->peer_count < NUM_PEERS) {
tox_node_log(self, "Peer joined: %u", peer_id);
state->peers[state->peer_count++] = peer_id;
}
}
static void on_group_topic(const Tox_Event_Group_Topic *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
SyncState *state = (SyncState *)tox_node_get_script_ctx(self);
state->topic_len = tox_event_group_topic_get_topic_length(event);
memcpy(state->topic, tox_event_group_topic_get_topic(event), state->topic_len);
state->topic[state->topic_len] = '\0';
tox_node_log(self, "Topic updated to: %s", state->topic);
}
static void common_init(ToxNode *self, SyncState *state)
{
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_self_join(dispatch, on_group_self_join);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_topic(dispatch, on_group_topic);
tox_node_log(self, "Waiting for self connection...");
tox_node_wait_for_self_connected(self);
tox_node_log(self, "Connected!");
}
static void founder_script(ToxNode *self, void *ctx)
{
SyncState *state = (SyncState *)ctx;
Tox *tox = tox_node_get_tox(self);
common_init(self, state);
Tox_Err_Group_New err_new;
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Founder", 7, &err_new);
ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
state->self_role = TOX_GROUP_ROLE_FOUNDER;
tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
// Phase 1: Wait for everyone to join
tox_scenario_barrier_wait(self); // Barrier 1: Created
tox_node_log(self, "Waiting for peers to join (current count: %u)...", state->peer_count);
WAIT_UNTIL(state->peer_count >= NUM_PEERS - 1);
tox_node_log(self, "All peers joined.");
// Phase 2: Topic Sync
tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr);
tox_scenario_barrier_wait(self); // Barrier 2: Lock disabled
// Everyone spams topic
tox_node_log(self, "Spamming topic...");
for (uint32_t i = 0; i < tox_node_get_index(self); ++i) {
tox_scenario_yield(self);
}
char topic[64];
snprintf(topic, sizeof(topic), "Founder Topic %d", rand());
bool ok = tox_group_set_topic(tox, state->group_number, (const uint8_t *)topic, strlen(topic), nullptr);
ck_assert(ok);
// Manually update state because Tox might not call on_group_topic for self
state->topic_len = strlen(topic);
memcpy(state->topic, topic, state->topic_len);
state->topic[state->topic_len] = '\0';
tox_scenario_barrier_wait(self); // Barrier 3: Topic spam done
// Wait for topic convergence
tox_node_log(self, "Waiting for topic convergence...");
uint64_t last_log = 0;
while (1) {
bool converged = true;
for (int i = 0; i < NUM_PEERS; ++i) {
const ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
const SyncState *s_view = (const SyncState *)tox_node_get_peer_ctx(node);
if (s_view->topic_len != state->topic_len || memcmp(s_view->topic, state->topic, state->topic_len) != 0) {
converged = false;
if (tox_scenario_get_time(tox_node_get_scenario(self)) > last_log + 5000) {
tox_node_log(self, "Still waiting for %s to converge topic. Expected: %s, Got: %s",
tox_node_get_alias(node), state->topic, s_view->topic);
}
break;
}
}
if (converged) {
break;
}
if (tox_scenario_get_time(tox_node_get_scenario(self)) > last_log + 5000) {
last_log = tox_scenario_get_time(tox_node_get_scenario(self));
}
tox_scenario_yield(self);
}
tox_node_log(self, "Topics converged!");
// Phase 3: Role Sync
// Promote everyone to Moderator
tox_node_log(self, "Promoting everyone to Moderator...");
for (uint32_t i = 0; i < state->peer_count; ++i) {
tox_group_set_role(tox, state->group_number, state->peers[i], TOX_GROUP_ROLE_MODERATOR, nullptr);
}
tox_scenario_barrier_wait(self); // Barrier 4: Roles set
// Wait for role convergence
tox_node_log(self, "Waiting for role convergence...");
while (1) {
bool converged = true;
for (int i = 0; i < NUM_PEERS; ++i) {
ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
const SyncState *s_view = (const SyncState *)tox_node_get_peer_ctx(node);
Tox_Group_Role expected = (i == 0 ? TOX_GROUP_ROLE_FOUNDER : TOX_GROUP_ROLE_MODERATOR);
if (s_view->self_role != expected) {
converged = false;
break;
}
}
if (converged) {
break;
}
tox_scenario_yield(self);
}
tox_node_log(self, "Roles converged!");
tox_scenario_barrier_wait(self); // Barrier 5: Done
}
static void peer_script(ToxNode *self, void *ctx)
{
SyncState *state = (SyncState *)ctx;
Tox *tox = tox_node_get_tox(self);
common_init(self, state);
const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
tox_scenario_barrier_wait(self); // Barrier 1: Created
const SyncState *founder_view = (const SyncState *)tox_node_get_peer_ctx(founder);
char name[16];
snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self));
state->group_number = tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, nullptr);
ck_assert(state->group_number != UINT32_MAX);
WAIT_UNTIL(state->connected);
tox_node_log(self, "Joined and connected to group.");
tox_scenario_barrier_wait(self); // Barrier 2: Lock disabled
// Spam topic
for (uint32_t i = 0; i < tox_node_get_index(self); ++i) {
tox_scenario_yield(self);
}
char topic[64];
snprintf(topic, sizeof(topic), "Peer%u Topic %d", tox_node_get_index(self), rand());
tox_node_log(self, "Setting topic: %s", topic);
tox_group_set_topic(tox, state->group_number, (const uint8_t *)topic, strlen(topic), nullptr);
// Manually update state because Tox might not call on_group_topic for self
state->topic_len = strlen(topic);
memcpy(state->topic, topic, state->topic_len);
state->topic[state->topic_len] = '\0';
tox_scenario_barrier_wait(self); // Barrier 3: Topic spam done
// Observe and publish role for Phase 3
tox_node_log(self, "Waiting for Moderator role...");
while (tox_scenario_is_running(self)) {
state->self_role = tox_group_self_get_role(tox, state->group_number, nullptr);
if (state->self_role == TOX_GROUP_ROLE_MODERATOR) {
break;
}
tox_scenario_yield(self);
}
tox_node_log(self, "Got Moderator role!");
tox_scenario_barrier_wait(self); // Barrier 4: Roles set
tox_scenario_barrier_wait(self); // Barrier 5: Done
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
SyncState states[NUM_PEERS] = {0};
ToxNode *nodes[NUM_PEERS];
nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(SyncState));
static char aliases[NUM_PEERS][16];
for (int i = 1; i < NUM_PEERS; ++i) {
snprintf(aliases[i], sizeof(aliases[i]), "Peer%d", i);
nodes[i] = tox_scenario_add_node(s, aliases[i], peer_script, &states[i], sizeof(SyncState));
}
for (int i = 0; i < NUM_PEERS; ++i) {
for (int j = 0; j < NUM_PEERS; ++j) {
if (i != j) {
tox_node_bootstrap(nodes[i], nodes[j]);
}
}
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef GROUP_NAME
#undef GROUP_NAME_LEN
#undef NUM_PEERS

View File

@@ -0,0 +1,234 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define CODEWORD "RONALD MCDONALD"
#define CODEWORD_LEN (sizeof(CODEWORD) - 1)
#define RELAY_TCP_PORT 33811
typedef struct {
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
uint32_t group_number;
uint32_t peer_id;
bool joined;
bool got_private_message;
bool got_group_message;
bool got_invite;
} State;
static void on_group_invite(const Tox_Event_Group_Invite *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
const uint32_t friend_number = tox_event_group_invite_get_friend_number(event);
const uint8_t *invite_data = tox_event_group_invite_get_invite_data(event);
const size_t length = tox_event_group_invite_get_invite_data_length(event);
Tox_Err_Group_Invite_Accept err_accept;
state->group_number = tox_group_invite_accept(tox_node_get_tox(self), friend_number, invite_data, length, (const uint8_t *)"test", 4,
nullptr, 0, &err_accept);
if (err_accept == TOX_ERR_GROUP_INVITE_ACCEPT_OK) {
state->got_invite = true;
}
}
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->peer_id = tox_event_group_peer_join_get_peer_id(event);
state->joined = true;
tox_node_log(self, "Peer %u joined group", state->peer_id);
}
static void on_group_private_message(const Tox_Event_Group_Private_Message *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
const uint8_t *message = tox_event_group_private_message_get_message(event);
const size_t length = tox_event_group_private_message_get_message_length(event);
if (length == CODEWORD_LEN && memcmp(CODEWORD, message, length) == 0) {
state->got_private_message = true;
}
}
static void on_group_message(const Tox_Event_Group_Message *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
const uint8_t *message = tox_event_group_message_get_message(event);
const size_t length = tox_event_group_message_get_message_length(event);
if (length == CODEWORD_LEN && memcmp(CODEWORD, message, length) == 0) {
state->got_group_message = true;
}
}
static void alice_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_events_callback_group_peer_join(tox_node_get_dispatch(self), on_group_peer_join);
Tox_Err_Group_New new_err;
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"test", 4,
(const uint8_t *)"test", 4, &new_err);
ck_assert(new_err == TOX_ERR_GROUP_NEW_OK);
Tox_Err_Group_State_Query id_err;
tox_group_get_chat_id(tox, state->group_number, state->chat_id, &id_err);
ck_assert(id_err == TOX_ERR_GROUP_STATE_QUERY_OK);
char chat_id_str[TOX_GROUP_CHAT_ID_SIZE * 2 + 1];
for (int i = 0; i < TOX_GROUP_CHAT_ID_SIZE; ++i) {
sprintf(chat_id_str + i * 2, "%02X", state->chat_id[i]);
}
tox_node_log(self, "Created group with chat_id: %s", chat_id_str);
tox_scenario_barrier_wait(self); // Barrier 1: chat_id is ready for Bob
tox_node_log(self, "Waiting for Bob to join group...");
WAIT_UNTIL(state->joined);
if (!state->joined) {
return;
}
Tox_Err_Group_Send_Private_Message perr;
tox_group_send_private_message(tox, state->group_number, state->peer_id,
TOX_MESSAGE_TYPE_NORMAL,
(const uint8_t *)CODEWORD, CODEWORD_LEN, &perr);
ck_assert(perr == TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK);
tox_scenario_barrier_wait(self); // Barrier 2: PM sent, Bob leaving
tox_scenario_barrier_wait(self); // Barrier 3: Bob left
state->joined = false;
tox_node_log(self, "Inviting Bob back via friend invite...");
Tox_Err_Group_Invite_Friend err_invite;
tox_group_invite_friend(tox, state->group_number, 0, &err_invite);
ck_assert(err_invite == TOX_ERR_GROUP_INVITE_FRIEND_OK);
WAIT_UNTIL(state->joined);
if (!state->joined) {
return;
}
Tox_Err_Group_Send_Message merr;
tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL,
(const uint8_t *)CODEWORD, CODEWORD_LEN, &merr);
ck_assert(merr == TOX_ERR_GROUP_SEND_MESSAGE_OK);
}
static void bob_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_events_callback_group_private_message(tox_node_get_dispatch(self), on_group_private_message);
tox_events_callback_group_message(tox_node_get_dispatch(self), on_group_message);
tox_events_callback_group_invite(tox_node_get_dispatch(self), on_group_invite);
tox_node_log(self, "Waiting for TCP connection...");
WAIT_UNTIL(tox_node_get_connection_status(self) == TOX_CONNECTION_TCP);
tox_node_log(self, "Connected. Waiting for Alice...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
if (!tox_node_is_friend_connected(self, 0)) {
tox_node_log(self, "TIMEOUT waiting for Alice.");
return;
}
tox_node_log(self, "Alice connected.");
tox_scenario_barrier_wait(self); // Barrier 1: Alice has chat_id
ToxScenario *s = tox_node_get_scenario(self);
ToxNode *alice_node = tox_scenario_get_node(s, 0);
State *alice_state = (State *)tox_node_get_script_ctx(alice_node);
tox_node_log(self, "Joining group via chat_id...");
Tox_Err_Group_Join jerr;
state->group_number = tox_group_join(tox, alice_state->chat_id, (const uint8_t *)"test", 4, nullptr, 0, &jerr);
ck_assert(jerr == TOX_ERR_GROUP_JOIN_OK);
WAIT_UNTIL(state->got_private_message);
if (!state->got_private_message) {
return;
}
tox_scenario_barrier_wait(self); // Barrier 2: PM received, now leaving
Tox_Err_Group_Leave err_exit;
tox_group_leave(tox, state->group_number, nullptr, 0, &err_exit);
ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK);
state->got_invite = false;
tox_scenario_barrier_wait(self); // Barrier 3: Left, waiting for invite
WAIT_UNTIL(state->got_invite);
WAIT_UNTIL(state->got_group_message);
}
static void relay_script(ToxNode *self, void *ctx)
{
(void)ctx;
tox_scenario_barrier_wait(self); // Barrier 1
tox_scenario_barrier_wait(self); // Barrier 2
tox_scenario_barrier_wait(self); // Barrier 3
}
int main(int argc, char **argv)
{
ToxScenario *s = tox_scenario_new(argc, argv, 120000);
struct Tox_Options *options_alice = tox_options_new(nullptr);
tox_options_set_udp_enabled(options_alice, false);
struct Tox_Options *options_bob = tox_options_new(nullptr);
tox_options_set_udp_enabled(options_bob, false);
struct Tox_Options *options_relay = tox_options_new(nullptr);
tox_options_set_tcp_port(options_relay, RELAY_TCP_PORT);
State *alice_state = (State *)calloc(1, sizeof(State));
State *bob_state = (State *)calloc(1, sizeof(State));
ToxNode *alice = tox_scenario_add_node_ex(s, "Alice", alice_script, alice_state, sizeof(State), options_alice);
ToxNode *bob = tox_scenario_add_node_ex(s, "Bob", bob_script, bob_state, sizeof(State), options_bob);
ToxNode *relay = tox_scenario_add_node_ex(s, "Relay", relay_script, nullptr, 0, options_relay);
tox_options_free(options_alice);
tox_options_free(options_bob);
tox_options_free(options_relay);
if (!alice || !bob || !relay) {
fprintf(stderr, "Failed to create nodes\n");
return 1;
}
uint8_t relay_dht_id[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox_node_get_tox(relay), relay_dht_id);
uint16_t relay_tcp_port = tox_self_get_tcp_port(tox_node_get_tox(relay), nullptr);
Tox_Err_Bootstrap berr;
// Both Alice and Bob use the Relay for TCP and DHT bootstrapping
tox_add_tcp_relay(tox_node_get_tox(alice), "127.0.0.1", relay_tcp_port, relay_dht_id, &berr);
tox_add_tcp_relay(tox_node_get_tox(bob), "127.0.0.1", relay_tcp_port, relay_dht_id, &berr);
tox_node_bootstrap(alice, relay);
tox_node_bootstrap(bob, relay);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
tox_scenario_free(s);
free(alice_state);
free(bob_state);
return (res == TOX_SCENARIO_DONE) ? 0 : 1;
}
#undef RELAY_TCP_PORT

View File

@@ -0,0 +1,241 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "framework/framework.h"
#define NUM_PEERS 2
#define GROUP_NAME "Bug Repro Group"
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
#define TOPIC1 "Topic A"
#define TOPIC2 "Topic B"
typedef struct {
uint32_t group_number;
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
bool chat_id_ready;
bool connected;
uint32_t peer_ids[NUM_PEERS];
uint8_t last_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
size_t last_topic_len;
Tox_Group_Topic_Lock topic_lock;
} TopicState;
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
state->connected = true;
state->group_number = tox_event_group_self_join_get_group_number(event);
}
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event);
uint32_t group_number = tox_event_group_peer_join_get_group_number(event);
Tox_Err_Group_Peer_Query q_err;
size_t length
= tox_group_peer_get_name_size(tox_node_get_tox(self), group_number, peer_id, &q_err);
if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK && length > 0) {
uint8_t name[TOX_MAX_NAME_LENGTH + 1];
tox_group_peer_get_name(tox_node_get_tox(self), group_number, peer_id, name, &q_err);
if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK) {
name[length] = 0;
if (length >= 4 && memcmp(name, "Peer", 4) == 0) {
uint32_t idx = (uint32_t)atoi((const char *)name + 4);
if (idx < NUM_PEERS) {
state->peer_ids[idx] = peer_id;
}
} else if (length == 7 && memcmp(name, "Founder", 7) == 0) {
state->peer_ids[0] = peer_id;
}
}
}
}
static void on_group_topic(const Tox_Event_Group_Topic *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
state->last_topic_len = tox_event_group_topic_get_topic_length(event);
memcpy(state->last_topic, tox_event_group_topic_get_topic(event), state->last_topic_len);
}
static void on_group_topic_lock(const Tox_Event_Group_Topic_Lock *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
state->topic_lock = tox_event_group_topic_lock_get_topic_lock(event);
}
static void common_init(ToxNode *self, TopicState *state)
{
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_self_join(dispatch, on_group_self_join);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_topic(dispatch, on_group_topic);
tox_events_callback_group_topic_lock(dispatch, on_group_topic_lock);
for (uint32_t i = 0; i < NUM_PEERS; ++i) {
state->peer_ids[i] = UINT32_MAX;
}
state->topic_lock = TOX_GROUP_TOPIC_LOCK_ENABLED;
tox_node_wait_for_self_connected(self);
}
static bool topic_is(ToxNode *self, const char *topic)
{
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
size_t len = strlen(topic);
if (state->last_topic_len == len && memcmp(state->last_topic, topic, len) == 0) {
return true;
}
Tox_Err_Group_State_Query err;
size_t current_len
= tox_group_get_topic_size(tox_node_get_tox(self), state->group_number, &err);
if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_len == len) {
uint8_t current_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
tox_group_get_topic(tox_node_get_tox(self), state->group_number, current_topic, &err);
if (err == TOX_ERR_GROUP_STATE_QUERY_OK && memcmp(current_topic, topic, len) == 0) {
state->last_topic_len = current_len;
memcpy(state->last_topic, current_topic, current_len);
return true;
}
}
return false;
}
static bool topic_lock_is(ToxNode *self, Tox_Group_Topic_Lock lock)
{
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
if (state->topic_lock == lock) {
return true;
}
Tox_Err_Group_State_Query err;
Tox_Group_Topic_Lock current_lock
= tox_group_get_topic_lock(tox_node_get_tox(self), state->group_number, &err);
if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_lock == lock) {
state->topic_lock = current_lock;
return true;
}
return false;
}
static void founder_script(ToxNode *self, void *ctx)
{
TopicState *state = (TopicState *)ctx;
Tox *tox = tox_node_get_tox(self);
common_init(self, state);
Tox_Err_Group_New err_new;
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC,
(const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Founder", 7, &err_new);
ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
state->peer_ids[0] = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
state->chat_id_ready = true;
tox_scenario_barrier_wait(self); // 1: Joined
WAIT_UNTIL(state->peer_ids[1] != UINT32_MAX);
tox_scenario_barrier_wait(self); // 2: Sync IDs
// Disable topic lock
tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr);
WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED));
tox_scenario_barrier_wait(self); // 3: Lock disabled
// Set Topic A
tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC1, strlen(TOPIC1), nullptr);
WAIT_UNTIL(topic_is(self, TOPIC1));
tox_scenario_barrier_wait(self); // 4: Topic A set
// Set Topic B
tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC2, strlen(TOPIC2), nullptr);
WAIT_UNTIL(topic_is(self, TOPIC2));
tox_scenario_barrier_wait(self); // 5: Topic B set
// Peer 1 will now set Topic A.
// We expect to REJECT it (stay on Topic B).
// But if bug exists, we might Accept it (revert to Topic A).
tox_scenario_barrier_wait(self); // 6: Peer 1 sets Topic A
// Verify we have Topic B.
// Wait a bit to ensure potential network packets arrived.
uint64_t start = tox_scenario_get_time(tox_node_get_scenario(self));
while (tox_scenario_get_time(tox_node_get_scenario(self)) - start < 1000) {
tox_scenario_yield(self);
if (topic_is(self, TOPIC1)) {
ck_assert_msg(false, "BUG REPRODUCED: Founder reverted to Topic A!");
}
}
ck_assert_msg(topic_is(self, TOPIC2), "Founder should be on Topic B");
}
static void peer_script(ToxNode *self, void *ctx)
{
TopicState *state = (TopicState *)ctx;
Tox *tox = tox_node_get_tox(self);
common_init(self, state);
ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const TopicState *founder_view = (const TopicState *)tox_node_get_peer_ctx(founder);
WAIT_UNTIL(founder_view->chat_id_ready);
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer1", 5, nullptr, 0, nullptr);
WAIT_UNTIL(state->connected);
state->peer_ids[1] = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
tox_scenario_barrier_wait(self); // 1: Joined
WAIT_UNTIL(state->peer_ids[0] != UINT32_MAX);
tox_scenario_barrier_wait(self); // 2: Sync IDs
WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED));
tox_scenario_barrier_wait(self); // 3: Lock disabled
WAIT_UNTIL(topic_is(self, TOPIC1));
tox_scenario_barrier_wait(self); // 4: Topic A set
WAIT_UNTIL(topic_is(self, TOPIC2));
tox_scenario_barrier_wait(self); // 5: Topic B set
// Now we set Topic A.
// This simulates an old topic being re-broadcast or a malicious/accidental revert.
tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC1, strlen(TOPIC1), nullptr);
tox_scenario_barrier_wait(self); // 6: Peer 1 sets Topic A
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
TopicState states[NUM_PEERS] = {0};
ToxNode *nodes[NUM_PEERS];
nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(TopicState));
nodes[1] = tox_scenario_add_node(s, "Peer1", peer_script, &states[1], sizeof(TopicState));
tox_node_bootstrap(nodes[0], nodes[1]);
tox_node_friend_add(nodes[0], nodes[1]);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef TOPIC2
#undef TOPIC1
#undef GROUP_NAME_LEN
#undef GROUP_NAME
#undef NUM_PEERS

View File

@@ -0,0 +1,310 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_PEERS 3
#define GROUP_NAME "The Test Chamber"
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
#define TOPIC1 "Topic One"
#define TOPIC2 "Topic Two"
typedef struct {
uint32_t group_number;
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
bool chat_id_ready;
bool connected;
uint32_t peer_ids[NUM_PEERS];
uint8_t last_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
size_t last_topic_len;
Tox_Group_Topic_Lock topic_lock;
Tox_Group_Role self_role;
} TopicState;
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
state->connected = true;
state->group_number = tox_event_group_self_join_get_group_number(event);
state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, nullptr);
}
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event);
uint32_t group_number = tox_event_group_peer_join_get_group_number(event);
Tox_Err_Group_Peer_Query q_err;
size_t length = tox_group_peer_get_name_size(tox_node_get_tox(self), group_number, peer_id, &q_err);
if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK && length > 0) {
uint8_t name[TOX_MAX_NAME_LENGTH + 1];
tox_group_peer_get_name(tox_node_get_tox(self), group_number, peer_id, name, &q_err);
if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK) {
name[length] = 0;
if (length >= 4 && memcmp(name, "Peer", 4) == 0) {
uint32_t idx = (uint32_t)atoi((const char *)name + 4);
if (idx < NUM_PEERS) {
state->peer_ids[idx] = peer_id;
}
}
}
}
}
static void on_group_topic(const Tox_Event_Group_Topic *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
state->last_topic_len = tox_event_group_topic_get_topic_length(event);
memcpy(state->last_topic, tox_event_group_topic_get_topic(event), state->last_topic_len);
}
static void on_group_topic_lock(const Tox_Event_Group_Topic_Lock *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
state->topic_lock = tox_event_group_topic_lock_get_topic_lock(event);
}
static void on_group_moderation(const Tox_Event_Group_Moderation *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
Tox_Err_Group_Self_Query err;
state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, &err);
}
static void common_init(ToxNode *self, TopicState *state)
{
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_group_self_join(dispatch, on_group_self_join);
tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
tox_events_callback_group_topic(dispatch, on_group_topic);
tox_events_callback_group_topic_lock(dispatch, on_group_topic_lock);
tox_events_callback_group_moderation(dispatch, on_group_moderation);
for (uint32_t i = 0; i < NUM_PEERS; ++i) {
state->peer_ids[i] = UINT32_MAX;
}
state->topic_lock = TOX_GROUP_TOPIC_LOCK_ENABLED;
tox_node_wait_for_self_connected(self);
}
static bool topic_is(ToxNode *self, const char *topic)
{
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
size_t len = strlen(topic);
if (state->last_topic_len == len && memcmp(state->last_topic, topic, len) == 0) {
return true;
}
Tox_Err_Group_State_Query err;
size_t current_len = tox_group_get_topic_size(tox_node_get_tox(self), state->group_number, &err);
if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_len == len) {
uint8_t current_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
tox_group_get_topic(tox_node_get_tox(self), state->group_number, current_topic, &err);
if (err == TOX_ERR_GROUP_STATE_QUERY_OK && memcmp(current_topic, topic, len) == 0) {
state->last_topic_len = current_len;
memcpy(state->last_topic, current_topic, current_len);
return true;
}
}
return false;
}
static bool topic_lock_is(ToxNode *self, Tox_Group_Topic_Lock lock)
{
TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
if (state->topic_lock == lock) {
return true;
}
Tox_Err_Group_State_Query err;
Tox_Group_Topic_Lock current_lock = tox_group_get_topic_lock(tox_node_get_tox(self), state->group_number, &err);
if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_lock == lock) {
state->topic_lock = current_lock;
return true;
}
return false;
}
static void founder_script(ToxNode *self, void *ctx)
{
TopicState *state = (TopicState *)ctx;
Tox *tox = tox_node_get_tox(self);
common_init(self, state);
Tox_Err_Group_New err_new;
state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Peer0", 5, &err_new);
ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
state->peer_ids[0] = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
state->chat_id_ready = true;
tox_scenario_barrier_wait(self); // 1: Peers joined
for (uint32_t i = 1; i < NUM_PEERS; ++i) {
WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX);
}
tox_scenario_barrier_wait(self); // 2: Sync IDs
// 3: Initial Topic
tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC1, strlen(TOPIC1), nullptr);
WAIT_UNTIL(topic_is(self, TOPIC1));
tox_scenario_barrier_wait(self);
// 4: Disable topic lock
tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr);
WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED));
tox_scenario_barrier_wait(self);
// 5: Peers changing topic
tox_scenario_barrier_wait(self); // Peer 1 turn
WAIT_UNTIL(topic_is(self, "Topic from Peer1"));
tox_scenario_barrier_wait(self); // Peer 2 turn
WAIT_UNTIL(topic_is(self, "Topic from Peer2"));
// 6: Set Peer 2 to Observer
tox_group_set_role(tox, state->group_number, state->peer_ids[2], TOX_GROUP_ROLE_OBSERVER, nullptr);
tox_scenario_barrier_wait(self);
// 7: Peer 1 changes topic, Peer 2 should fail
tox_scenario_barrier_wait(self); // Peer 1 turn
WAIT_UNTIL(topic_is(self, "Topic again from Peer1"));
tox_scenario_barrier_wait(self); // Peer 2 turn (fails)
// 8: Enable topic lock
tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_ENABLED, nullptr);
WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_ENABLED));
tox_scenario_barrier_wait(self);
// 9: Peer 1 attempts to change topic (fails)
tox_scenario_barrier_wait(self);
// 10: Founder changes topic
tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC2, strlen(TOPIC2), nullptr);
WAIT_UNTIL(topic_is(self, TOPIC2));
tox_scenario_barrier_wait(self);
}
static void peer_script(ToxNode *self, void *ctx)
{
TopicState *state = (TopicState *)ctx;
Tox *tox = tox_node_get_tox(self);
common_init(self, state);
ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const TopicState *founder_view = (const TopicState *)tox_node_get_peer_ctx(founder);
WAIT_UNTIL(founder_view->chat_id_ready);
char name[16];
snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self));
tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, nullptr);
WAIT_UNTIL(state->connected);
state->peer_ids[tox_node_get_index(self)] = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
tox_scenario_barrier_wait(self); // 1: Joined
for (uint32_t i = 0; i < NUM_PEERS; ++i) if (i != tox_node_get_index(self)) {
WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX);
}
tox_scenario_barrier_wait(self); // 2: Sync IDs
// 3: Topic check
WAIT_UNTIL(topic_is(self, TOPIC1));
tox_scenario_barrier_wait(self);
// 4: Disable topic lock
WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED));
tox_scenario_barrier_wait(self);
// 5: Peers changing topic
if (tox_node_get_index(self) == 1) {
tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic from Peer1", strlen("Topic from Peer1"), nullptr);
}
tox_scenario_barrier_wait(self); // Peer 1 turn
WAIT_UNTIL(topic_is(self, "Topic from Peer1"));
if (tox_node_get_index(self) == 2) {
tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic from Peer2", strlen("Topic from Peer2"), nullptr);
}
tox_scenario_barrier_wait(self); // Peer 2 turn
WAIT_UNTIL(topic_is(self, "Topic from Peer2"));
// 6: Set Peer 2 to Observer
tox_scenario_barrier_wait(self);
if (tox_node_get_index(self) == 2) {
WAIT_UNTIL(state->self_role == TOX_GROUP_ROLE_OBSERVER);
}
// 7: Peer 1 changes topic, Peer 2 should fail
if (tox_node_get_index(self) == 1) {
tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic again from Peer1", strlen("Topic again from Peer1"), nullptr);
}
tox_scenario_barrier_wait(self); // Peer 1 turn
WAIT_UNTIL(topic_is(self, "Topic again from Peer1"));
if (tox_node_get_index(self) == 2) {
Tox_Err_Group_Topic_Set err;
tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Fail topic", 10, &err);
ck_assert(err == TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS);
}
tox_scenario_barrier_wait(self); // Peer 2 turn (fails)
// 8: Enable topic lock
WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_ENABLED));
tox_scenario_barrier_wait(self);
// 9: Peer 1 attempts to change topic (fails)
if (tox_node_get_index(self) == 1) {
Tox_Err_Group_Topic_Set err;
tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Fail topic 2", 12, &err);
ck_assert(err == TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS);
}
tox_scenario_barrier_wait(self);
// 10: Founder changes topic
tox_scenario_barrier_wait(self);
WAIT_UNTIL(topic_is(self, TOPIC2));
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
TopicState states[NUM_PEERS] = {0};
ToxNode *nodes[NUM_PEERS];
nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(TopicState));
for (uint32_t i = 1; i < NUM_PEERS; ++i) {
char alias[16];
snprintf(alias, sizeof(alias), "Peer%u", i);
nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(TopicState));
}
for (uint32_t i = 0; i < NUM_PEERS; ++i) {
for (uint32_t j = 0; j < NUM_PEERS; ++j) {
if (i != j) {
tox_node_bootstrap(nodes[i], nodes[j]);
tox_node_friend_add(nodes[i], nodes[j]);
}
}
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef GROUP_NAME
#undef GROUP_NAME_LEN
#undef NUM_PEERS

View File

@@ -0,0 +1,51 @@
#include "framework/framework.h"
#include <stdio.h>
static void alice_script(ToxNode *self, void *ctx)
{
tox_node_log(self, "Waiting for LAN discovery...");
WAIT_UNTIL(tox_node_is_self_connected(self));
if (tox_node_is_self_connected(self)) {
tox_node_log(self, "Discovered network via LAN!");
} else {
tox_node_log(self, "Failed to discover network via LAN.");
}
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_log(self, "Waiting for LAN discovery...");
WAIT_UNTIL(tox_node_is_self_connected(self));
if (tox_node_is_self_connected(self)) {
tox_node_log(self, "Discovered network via LAN!");
} else {
tox_node_log(self, "Failed to discover network via LAN.");
}
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
Tox_Options *opts = tox_options_new(nullptr);
tox_options_set_ipv6_enabled(opts, false);
tox_options_set_local_discovery_enabled(opts, true);
// Both nodes have local discovery enabled and NO bootstrap
tox_scenario_add_node_ex(s, "Alice", alice_script, nullptr, 0, opts);
tox_scenario_add_node_ex(s, "Bob", bob_script, nullptr, 0, opts);
tox_options_free(opts);
tox_scenario_log(s, "Starting scenario (LAN Discovery).");
ToxScenarioStatus res = tox_scenario_run(s);
if (res == TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Scenario completed successfully!");
} else {
tox_scenario_log(s, "Scenario failed with status: %u", res);
}
tox_scenario_free(s);
return (res == TOX_SCENARIO_DONE) ? 0 : 1;
}

View File

@@ -0,0 +1,68 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LOSSLESS_PACKET_FILLER 160
static void alice_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
tox_node_log(self, "Waiting for connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Self connected, waiting for friend Bob...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to Bob. Connection type: %u", tox_friend_get_connection_status(tox, 0, nullptr));
const size_t packet_size = tox_max_custom_packet_size();
uint8_t *packet = (uint8_t *)malloc(packet_size);
memset(packet, LOSSLESS_PACKET_FILLER, packet_size);
tox_node_log(self, "Sending lossless packet to Bob (size %zu)...", packet_size);
Tox_Err_Friend_Custom_Packet err;
if (tox_friend_send_lossless_packet(tox, 0, packet, packet_size, &err)) {
tox_node_log(self, "Lossless packet sent successfully!");
} else {
tox_node_log(self, "Failed to send lossless packet: %u", err);
}
free(packet);
}
static void bob_script(ToxNode *self, void *ctx)
{
const Tox *tox = tox_node_get_tox(self);
tox_node_log(self, "Waiting for connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Self connected, waiting for friend Alice...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to Alice. Connection type: %u", tox_friend_get_connection_status(tox, 0, nullptr));
tox_node_log(self, "Waiting for lossless packet from Alice...");
WAIT_FOR_EVENT(TOX_EVENT_FRIEND_LOSSLESS_PACKET);
if (tox_scenario_is_running(self)) {
tox_node_log(self, "Received lossless packet from Alice!");
} else {
tox_node_log(self, "Timed out waiting for lossless packet.");
}
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,52 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LOSSY_PACKET_FILLER 200
static void alice_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
const size_t packet_size = tox_max_custom_packet_size();
uint8_t *packet = (uint8_t *)malloc(packet_size);
memset(packet, LOSSY_PACKET_FILLER, packet_size);
tox_node_log(self, "Sending lossy packet to Bob...");
tox_friend_send_lossy_packet(tox, 0, packet, packet_size, nullptr);
free(packet);
}
static void bob_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Waiting for lossy packet from Alice...");
WAIT_FOR_EVENT(TOX_EVENT_FRIEND_LOSSY_PACKET);
tox_node_log(self, "Received lossy packet from Alice!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,65 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
static void alice_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
tox_node_log(self, "Waiting for DHT connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Connected to DHT. Waiting for friend connection...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Sending message to Bob...");
uint8_t msg[] = "Hello Bob!";
Tox_Err_Friend_Send_Message err;
tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err);
if (err != TOX_ERR_FRIEND_SEND_MESSAGE_OK) {
return;
}
tox_node_log(self, "Waiting for response from Bob...");
WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE);
tox_node_log(self, "Received response from Bob!");
}
static void bob_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
tox_node_log(self, "Waiting for DHT connection...");
WAIT_UNTIL(tox_node_is_self_connected(self));
tox_node_log(self, "Connected to DHT. Waiting for friend connection...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Waiting for message from Alice...");
WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE);
tox_node_log(self, "Received message from Alice! Sending response...");
uint8_t msg[] = "Hello Alice!";
Tox_Err_Friend_Send_Message err;
tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000); // 60s virtual timeout
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
tox_scenario_log(s, "Starting scenario...");
ToxScenarioStatus res = tox_scenario_run(s);
if (res == TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Scenario completed successfully!");
} else {
tox_scenario_log(s, "Scenario failed with status: %u", res);
}
tox_scenario_free(s);
return (res == TOX_SCENARIO_DONE) ? 0 : 1;
}

View File

@@ -0,0 +1,61 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "../../toxcore/tox_private.h"
static void alice_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
for (int i = 0; i < 256; i++) {
tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"test", 4, nullptr);
tox_scenario_yield(self);
}
// Wait for Bob to also send his messages and for them to arrive.
for (int i = 0; i < 100; i++) {
tox_scenario_yield(self);
}
const Tox *tox = tox_node_get_tox(self);
uint64_t udp_sent = tox_netprof_get_packet_total_count(tox, TOX_NETPROF_PACKET_TYPE_UDP, TOX_NETPROF_DIRECTION_SENT);
uint64_t udp_recv = tox_netprof_get_packet_total_count(tox, TOX_NETPROF_PACKET_TYPE_UDP, TOX_NETPROF_DIRECTION_RECV);
tox_node_log(self, "UDP Sent: %" PRIu64 ", Received: %" PRIu64, udp_sent, udp_recv);
ck_assert(udp_sent >= 256);
ck_assert(udp_recv >= 1);
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
for (int i = 0; i < 256; i++) {
tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"test", 4, nullptr);
tox_scenario_yield(self);
}
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,105 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
bool nospam_changed;
} AliceState;
static void set_checksum(uint8_t *address)
{
uint8_t checksum[2] = {0};
for (int i = 0; i < 36; ++i) {
checksum[i % 2] ^= address[i];
}
address[36] = checksum[0];
address[37] = checksum[1];
}
static void alice_script(ToxNode *self, void *ctx)
{
AliceState *state = (AliceState *)ctx;
tox_node_wait_for_self_connected(self);
Tox *tox = tox_node_get_tox(self);
uint32_t old_nospam = tox_self_get_nospam(tox);
uint32_t new_nospam = old_nospam + 1;
tox_node_log(self, "Old nospam: %u, setting new nospam: %u", old_nospam, new_nospam);
tox_self_set_nospam(tox, new_nospam);
ck_assert(tox_self_get_nospam(tox) == new_nospam);
// Signal to Bob that we changed nospam
state->nospam_changed = true;
tox_node_log(self, "Waiting for friend request from Bob...");
WAIT_FOR_EVENT(TOX_EVENT_FRIEND_REQUEST);
tox_node_log(self, "Received friend request from Bob!");
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0);
const AliceState *alice_view = (const AliceState *)tox_node_get_peer_ctx(alice);
tox_node_log(self, "Waiting for Alice to change nospam...");
WAIT_UNTIL(alice_view->nospam_changed);
uint8_t alice_addr[TOX_ADDRESS_SIZE];
tox_node_get_address(alice, alice_addr);
// Alice's address in alice_addr is already the NEW one.
// Manually construct an old address.
uint8_t old_alice_addr[TOX_ADDRESS_SIZE];
memcpy(old_alice_addr, alice_addr, TOX_ADDRESS_SIZE);
uint32_t nospam;
memcpy(&nospam, old_alice_addr + 32, 4);
nospam--; // Revert to old nospam
memcpy(old_alice_addr + 32, &nospam, 4);
set_checksum(old_alice_addr);
tox_node_log(self, "Trying to add Alice with OLD nospam...");
Tox_Err_Friend_Add err;
uint32_t friend_num = tox_friend_add(tox_node_get_tox(self), old_alice_addr, (const uint8_t *)"Hi", 2, &err);
ck_assert(err == TOX_ERR_FRIEND_ADD_OK);
// Wait some time, but not too long.
for (int i = 0; i < 100; ++i) {
tox_scenario_yield(self);
}
tox_node_log(self, "Deleting Alice and trying with NEW nospam...");
tox_friend_delete(tox_node_get_tox(self), friend_num, nullptr);
tox_friend_add(tox_node_get_tox(self), alice_addr, (const uint8_t *)"Hi", 2, &err);
ck_assert(err == TOX_ERR_FRIEND_ADD_OK);
tox_node_log(self, "Friend request sent!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
AliceState alice_state = {false};
tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState));
tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_bootstrap(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,125 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#define NUM_MSGS 40000
typedef struct {
uint32_t recv_count;
} State;
static void on_friend_message(const Tox_Event_Friend_Message *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->recv_count++;
}
static void receiver_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
tox_events_callback_friend_message(dispatch, on_friend_message);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
tox_node_wait_for_friend_connected(self, 1);
tox_node_log(self, "Ready to receive...");
tox_scenario_barrier_wait(self); // Barrier 1: Connected
// Wait until we get many messages or scenario timeout.
// We don't expect ALL 80k messages to arrive in a short time,
// but we want to see if it survives the flood.
uint32_t last_count = 0;
uint32_t stable_ticks = 0;
while (tox_scenario_is_running(self)) {
tox_scenario_yield(self);
if (state->recv_count == last_count && state->recv_count > 0) {
stable_ticks++;
if (stable_ticks > 100) {
break; // No new messages for 5 seconds
}
} else {
stable_ticks = 0;
}
last_count = state->recv_count;
if (state->recv_count >= NUM_MSGS * 2) {
break;
}
}
tox_node_log(self, "Received %u messages.", state->recv_count);
}
static void sender_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
tox_scenario_barrier_wait(self); // Barrier 1: Connected
tox_node_log(self, "Sending messages...");
Tox *tox = tox_node_get_tox(self);
for (uint32_t i = 0; i < NUM_MSGS; i++) {
uint8_t message[128] = {0};
snprintf((char *)message, sizeof(message), "%u-%u", tox_node_get_index(self), i);
Tox_Err_Friend_Send_Message err;
tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof(message), &err);
if (err == TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ) {
// This is expected when flooding
if (i % 1000 == 0) {
tox_node_log(self, "Send queue full at message %u, yielding...", i);
}
tox_scenario_yield(self);
i--; // Retry this message
continue;
}
if (err == TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED) {
if (i % 1000 == 0) {
tox_node_log(self, "Friend not connected at message %u, waiting...", i);
}
tox_node_wait_for_friend_connected(self, 0);
i--; // Retry this message
continue;
}
ck_assert_msg(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK, "send_message failed with %s",
tox_err_friend_send_message_to_string(err));
if (i % 500 == 0) {
tox_scenario_yield(self);
}
}
tox_node_log(self, "Finished sending.");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 120000);
State receiver_state = {0};
ToxNode *receiver = tox_scenario_add_node(s, "Receiver", receiver_script, &receiver_state, sizeof(State));
ToxNode *sender1 = tox_scenario_add_node(s, "Sender1", sender_script, nullptr, 0);
ToxNode *sender2 = tox_scenario_add_node(s, "Sender2", sender_script, nullptr, 0);
tox_node_bootstrap(sender1, receiver);
tox_node_bootstrap(sender2, receiver);
tox_node_friend_add(receiver, sender1);
tox_node_friend_add(sender1, receiver);
tox_node_friend_add(receiver, sender2);
tox_node_friend_add(sender2, receiver);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE && res != TOX_SCENARIO_TIMEOUT) {
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef NUM_MSGS

View File

@@ -0,0 +1,63 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#define NUM_MSGS 40000
static void alice_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
const uint8_t message[] = {0};
bool errored = false;
Tox *tox = tox_node_get_tox(self);
for (uint32_t i = 0; i < NUM_MSGS; i++) {
Tox_Err_Friend_Send_Message err;
tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof(message), &err);
if (err != TOX_ERR_FRIEND_SEND_MESSAGE_OK) {
errored = true;
}
if (errored) {
ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ);
} else {
ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK);
}
}
ck_assert(errored);
tox_node_log(self, "Success: Send queue overflowed as expected.");
}
static void bob_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0);
WAIT_UNTIL(tox_node_is_finished(alice));
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 30000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef NUM_MSGS

View File

@@ -0,0 +1,61 @@
#include "framework/framework.h"
#include <stdio.h>
static void alice_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to Bob.");
// Bob will go offline now.
tox_node_log(self, "Waiting for Bob to time out...");
WAIT_UNTIL(!tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Bob timed out as expected.");
tox_node_log(self, "Waiting for Bob to reconnect...");
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Bob reconnected!");
}
static void bob_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to Alice. Going offline for 40 seconds...");
tox_node_set_offline(self, true);
// In our virtual clock, 40 seconds is 40000 / TOX_SCENARIO_TICK_MS ticks.
for (int i = 0; i < 40000 / TOX_SCENARIO_TICK_MS; ++i) {
tox_scenario_yield(self);
}
tox_node_log(self, "Coming back online.");
tox_node_set_offline(self, false);
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Reconnected to Alice!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
tox_node_bootstrap(alice, bob);
tox_node_bootstrap(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,93 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
uint8_t *name;
uint8_t *status_message;
size_t name_len;
size_t status_len;
} ReferenceData;
static void alice_script(ToxNode *self, void *ctx)
{
const ReferenceData *ref = (const ReferenceData *)ctx;
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
// Wait for name and status to propagate
WAIT_UNTIL(tox_node_friend_name_is(self, 0, ref->name, ref->name_len));
WAIT_UNTIL(tox_node_friend_status_message_is(self, 0, ref->status_message, ref->status_len));
tox_node_log(self, "Received reference name and status from Bob");
// Save and Reload
tox_node_log(self, "Reloading...");
tox_node_reload(self);
tox_node_log(self, "Reloaded");
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
// Check if Bob's info is still correct after reload
ck_assert(tox_node_friend_name_is(self, 0, ref->name, ref->name_len));
ck_assert(tox_node_friend_status_message_is(self, 0, ref->status_message, ref->status_len));
tox_node_log(self, "Bob's name and status are still correct after reload");
}
static void bob_script(ToxNode *self, void *ctx)
{
ReferenceData *ref = (ReferenceData *)ctx;
Tox *tox = tox_node_get_tox(self);
tox_self_set_name(tox, ref->name, ref->name_len, nullptr);
tox_self_set_status_message(tox, ref->status_message, ref->status_len, nullptr);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
// Stay online for Alice
tox_node_log(self, "Waiting for Alice to finish...");
while (!tox_node_is_finished(tox_scenario_get_node(tox_node_get_scenario(self), 0))) {
tox_scenario_yield(self);
}
tox_node_log(self, "Alice finished, Bob is done");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
ReferenceData ref;
ref.name_len = tox_max_name_length();
ref.status_len = tox_max_status_message_length();
ref.name = (uint8_t *)malloc(ref.name_len);
ref.status_message = (uint8_t *)malloc(ref.status_len);
for (size_t i = 0; i < ref.name_len; ++i) {
ref.name[i] = (uint8_t)(rand() % 256);
}
for (size_t i = 0; i < ref.status_len; ++i) {
ref.status_message[i] = (uint8_t)(rand() % 256);
}
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, &ref, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &ref, 0);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
tox_node_bootstrap(alice, bob);
tox_node_bootstrap(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
free(ref.name);
free(ref.status_message);
return 0;
}

View File

@@ -0,0 +1,90 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define FRIEND_REQUEST_MSG "Gentoo"
static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
const uint8_t *msg = tox_event_friend_request_get_message(event);
size_t len = tox_event_friend_request_get_message_length(event);
if (len == 7 && memcmp(msg, FRIEND_REQUEST_MSG, 7) == 0) {
tox_friend_add_norequest(tox_node_get_tox(self), tox_event_friend_request_get_public_key(event), nullptr);
}
}
static void relay_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
ToxScenario *s = tox_node_get_scenario(self);
bool peers_done = false;
while (!peers_done && tox_scenario_is_running(self)) {
peers_done = true;
for (uint32_t i = 1; i < 3; ++i) {
if (!tox_node_is_finished(tox_scenario_get_node(s, i))) {
peers_done = false;
break;
}
}
if (!peers_done) {
tox_scenario_yield(self);
}
}
}
static void peer_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
uint32_t my_index = tox_node_get_index(self);
if (my_index == 1) {
tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request);
}
WAIT_UNTIL(tox_node_is_self_connected(self));
if (my_index == 2) {
ToxNode *peer1 = tox_scenario_get_node(tox_node_get_scenario(self), 1);
uint8_t addr[TOX_ADDRESS_SIZE];
tox_node_get_address(peer1, addr);
tox_friend_add(tox, addr, (const uint8_t *)FRIEND_REQUEST_MSG, 7, nullptr);
}
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to friend");
// Reload test
tox_node_log(self, "Reloading...");
tox_node_reload(self);
tox_node_log(self, "Reloaded");
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Connected to friend again after reload");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
ToxNode *relay = tox_scenario_add_node(s, "Relay", relay_script, nullptr, 0);
ToxNode *peer1 = tox_scenario_add_node(s, "Peer1", peer_script, nullptr, 0);
ToxNode *peer2 = tox_scenario_add_node(s, "Peer2", peer_script, nullptr, 0);
// All peers bootstrap from relay
tox_node_bootstrap(peer1, relay);
tox_node_bootstrap(peer2, relay);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,92 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
bool connection_status_called;
Tox_Connection last_status;
} State;
static void on_self_connection_status(const Tox_Event_Self_Connection_Status *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
State *state = (State *)tox_node_get_script_ctx(self);
state->connection_status_called = true;
state->last_status = tox_event_self_connection_status_get_connection_status(event);
tox_node_log(self, "Self connection status: %s", tox_connection_to_string(state->last_status));
}
static void alice_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
// Register dispatch callback
tox_events_callback_self_connection_status(tox_node_get_dispatch(self), on_self_connection_status);
tox_node_wait_for_self_connected(self);
ck_assert(state->connection_status_called);
ck_assert(state->last_status != TOX_CONNECTION_NONE);
Tox *tox = tox_node_get_tox(self);
// Test ports
Tox_Err_Get_Port err_port;
uint16_t udp_port = tox_self_get_udp_port(tox, &err_port);
ck_assert(err_port == TOX_ERR_GET_PORT_OK);
ck_assert(udp_port > 0);
tox_node_log(self, "UDP Port: %u", udp_port);
// Friend list test
tox_node_wait_for_friend_connected(self, 0);
tox_node_wait_for_friend_connected(self, 1);
size_t friend_count = tox_self_get_friend_list_size(tox);
ck_assert(friend_count == 2);
uint32_t friends[2];
tox_self_get_friend_list(tox, friends);
ck_assert(friends[0] == 0);
ck_assert(friends[1] == 1);
tox_node_log(self, "Self query tests passed!");
}
static void peer_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0);
WAIT_UNTIL(tox_node_is_finished(alice));
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
State alice_state = {false, TOX_CONNECTION_NONE};
tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(State));
tox_scenario_add_node(s, "Bob", peer_script, nullptr, 0);
tox_scenario_add_node(s, "Charlie", peer_script, nullptr, 0);
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
ToxNode *charlie = tox_scenario_get_node(s, 2);
tox_node_bootstrap(alice, bob);
tox_node_bootstrap(charlie, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
tox_node_friend_add(alice, charlie);
tox_node_friend_add(charlie, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,98 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MESSAGE_FILLER 'G'
typedef struct {
bool message_received;
uint32_t received_len;
} MessageState;
static void on_friend_message(const Tox_Event_Friend_Message *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
MessageState *state = (MessageState *)tox_node_get_script_ctx(self);
state->received_len = tox_event_friend_message_get_message_length(event);
const uint8_t *msg = tox_event_friend_message_get_message(event);
size_t max_len = tox_max_message_length();
bool correct = (state->received_len == max_len);
if (correct) {
for (size_t i = 0; i < max_len; ++i) {
if (msg[i] != MESSAGE_FILLER) {
correct = false;
break;
}
}
}
if (correct) {
state->message_received = true;
tox_node_log(self, "Received correct long message");
} else {
tox_node_log(self, "Received INCORRECT message, len %u", state->received_len);
}
}
static void sender_script(ToxNode *self, void *ctx)
{
Tox *tox = tox_node_get_tox(self);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
size_t max_len = tox_max_message_length();
uint8_t *msg = (uint8_t *)malloc(max_len + 1);
memset(msg, MESSAGE_FILLER, max_len + 1);
Tox_Err_Friend_Send_Message err;
// Test too long
tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, max_len + 1, &err);
ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG);
tox_node_log(self, "Correctly failed to send too long message");
// Test max length
tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, max_len, &err);
ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK);
tox_node_log(self, "Sent max length message");
free(msg);
}
static void receiver_script(ToxNode *self, void *ctx)
{
const MessageState *state = (const MessageState *)ctx;
tox_events_callback_friend_message(tox_node_get_dispatch(self), on_friend_message);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
WAIT_UNTIL(state->message_received);
tox_node_log(self, "Done");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
MessageState state_receiver = {0};
ToxNode *sender = tox_scenario_add_node(s, "Sender", sender_script, nullptr, 0);
ToxNode *receiver = tox_scenario_add_node(s, "Receiver", receiver_script, &state_receiver, sizeof(MessageState));
tox_node_friend_add(sender, receiver);
tox_node_friend_add(receiver, sender);
tox_node_bootstrap(sender, receiver);
tox_node_bootstrap(receiver, sender);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,45 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#define NICKNAME "Gentoo"
static void alice_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Setting name to %s", NICKNAME);
tox_self_set_name(tox_node_get_tox(self), (const uint8_t *)NICKNAME, sizeof(NICKNAME), nullptr);
}
static void bob_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Waiting for Alice to change name...");
WAIT_UNTIL(tox_node_friend_name_is(self, 0, (const uint8_t *)NICKNAME, sizeof(NICKNAME)));
tox_node_log(self, "Alice\'s name is now %s", NICKNAME);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,45 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#define STATUS_MESSAGE "Installing Gentoo"
static void alice_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Setting status message to %s", STATUS_MESSAGE);
tox_self_set_status_message(tox_node_get_tox(self), (const uint8_t *)STATUS_MESSAGE, sizeof(STATUS_MESSAGE), nullptr);
}
static void bob_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Waiting for Alice to change status message...");
WAIT_UNTIL(tox_node_friend_status_message_is(self, 0, (const uint8_t *)STATUS_MESSAGE, sizeof(STATUS_MESSAGE)));
tox_node_log(self, "Alice\'s status message is now %s", STATUS_MESSAGE);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,160 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_TOXES 40
#define NUM_FRIEND_PAIRS 50
#define FR_MESSAGE "Gentoo"
#define RELAY_TCP_PORT 33448
typedef struct {
uint32_t expected_friends;
} State;
static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
const uint8_t *msg = tox_event_friend_request_get_message(event);
size_t len = tox_event_friend_request_get_message_length(event);
if (len == 6 && memcmp(msg, FR_MESSAGE, 6) == 0) {
tox_friend_add_norequest(tox_node_get_tox(self), tox_event_friend_request_get_public_key(event), nullptr);
}
}
static void peer_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request);
tox_node_log(self, "Waiting for connection to relay...");
tox_node_wait_for_self_connected(self);
tox_node_log(self, "Connected. Expected friends: %u", state->expected_friends);
// Wait until we have the expected number of friends connected via TCP
uint32_t last_connected = 0;
while (1) {
uint32_t connected_friends = 0;
uint32_t total_friends = tox_self_get_friend_list_size(tox_node_get_tox(self));
for (uint32_t i = 0; i < total_friends; ++i) {
if (tox_node_get_friend_connection_status(self, i) == TOX_CONNECTION_TCP) {
connected_friends++;
}
}
if (connected_friends != last_connected) {
tox_node_log(self, "Connected friends: %u/%u", connected_friends, state->expected_friends);
last_connected = connected_friends;
}
if (connected_friends >= state->expected_friends) {
break;
}
tox_scenario_yield(self);
}
tox_node_log(self, "All %u friends connected via TCP.", state->expected_friends);
}
static void relay_script(ToxNode *self, void *ctx)
{
(void)ctx;
ToxScenario *s = tox_node_get_scenario(self);
while (tox_scenario_is_running(self)) {
bool all_finished = true;
for (uint32_t i = 0; i < NUM_TOXES; ++i) {
ToxNode *peer = tox_scenario_get_node(s, i + 1); // Peers start at index 1
if (!tox_node_is_finished(peer)) {
all_finished = false;
break;
}
}
if (all_finished) {
break;
}
tox_scenario_yield(self);
}
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
struct Tox_Options *opts_relay = tox_options_new(nullptr);
tox_options_set_tcp_port(opts_relay, RELAY_TCP_PORT);
ToxNode *relay = tox_scenario_add_node_ex(s, "Relay", relay_script, nullptr, 0, opts_relay);
tox_options_free(opts_relay);
uint8_t relay_dht_id[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox_node_get_tox(relay), relay_dht_id);
uint16_t relay_tcp_port = tox_self_get_tcp_port(tox_node_get_tox(relay), nullptr);
State states[NUM_TOXES] = {0};
ToxNode *nodes[NUM_TOXES];
struct Tox_Options *opts_peer = tox_options_new(nullptr);
tox_options_set_udp_enabled(opts_peer, false);
tox_options_set_local_discovery_enabled(opts_peer, false);
for (int i = 0; i < NUM_TOXES; ++i) {
char alias[16];
snprintf(alias, sizeof(alias), "Tox%d", i);
nodes[i] = tox_scenario_add_node_ex(s, alias, peer_script, &states[i], sizeof(State), opts_peer);
// All peers use the Relay for TCP and DHT bootstrapping
tox_add_tcp_relay(tox_node_get_tox(nodes[i]), "127.0.0.1", relay_tcp_port, relay_dht_id, nullptr);
tox_node_bootstrap(nodes[i], relay);
}
tox_options_free(opts_peer);
struct {
uint16_t t1;
uint16_t t2;
} pairs[NUM_FRIEND_PAIRS];
// Generate friend pairs
for (int i = 0; i < NUM_FRIEND_PAIRS; ++i) {
bool unique;
do {
unique = true;
pairs[i].t1 = rand() % NUM_TOXES;
pairs[i].t2 = (pairs[i].t1 + rand() % (NUM_TOXES - 1) + 1) % NUM_TOXES;
// Avoid reciprocal or duplicate pairs
for (int j = 0; j < i; ++j) {
if ((pairs[j].t1 == pairs[i].t1 && pairs[j].t2 == pairs[i].t2) ||
(pairs[j].t1 == pairs[i].t2 && pairs[j].t2 == pairs[i].t1)) {
unique = false;
break;
}
}
} while (!unique);
uint8_t addr[TOX_ADDRESS_SIZE];
tox_self_get_address(tox_node_get_tox(nodes[pairs[i].t1]), addr);
Tox_Err_Friend_Add err;
tox_friend_add(tox_node_get_tox(nodes[pairs[i].t2]), addr, (const uint8_t *)FR_MESSAGE, 6, &err);
ck_assert(err == TOX_ERR_FRIEND_ADD_OK);
states[pairs[i].t1].expected_friends++;
states[pairs[i].t2].expected_friends++;
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef FR_MESSAGE
#undef NUM_TOXES
#undef NUM_FRIEND_PAIRS
#undef RELAY_TCP_PORT

View File

@@ -0,0 +1,102 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_TOXES 90
#define NUM_FRIEND_PAIRS 50
#define FR_MESSAGE "Gentoo"
typedef struct {
uint32_t friend_count;
} State;
static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
const uint8_t *msg = tox_event_friend_request_get_message(event);
size_t len = tox_event_friend_request_get_message_length(event);
if (len == 6 && memcmp(msg, FR_MESSAGE, 6) == 0) {
tox_friend_add_norequest(tox_node_get_tox(self), tox_event_friend_request_get_public_key(event), nullptr);
}
}
static void peer_script(ToxNode *self, void *ctx)
{
State *state = (State *)ctx;
tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request);
tox_node_wait_for_self_connected(self);
// Wait until we have the expected number of friends connected
while (1) {
uint32_t connected_friends = 0;
uint32_t total_friends = tox_self_get_friend_list_size(tox_node_get_tox(self));
for (uint32_t i = 0; i < total_friends; ++i) {
if (tox_node_is_friend_connected(self, i)) {
connected_friends++;
}
}
if (connected_friends >= state->friend_count) {
break;
}
tox_scenario_yield(self);
}
tox_node_log(self, "All %u friends connected.", state->friend_count);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 300000);
State states[NUM_TOXES] = {0};
ToxNode *nodes[NUM_TOXES];
for (int i = 0; i < NUM_TOXES; ++i) {
char alias[16];
snprintf(alias, sizeof(alias), "Tox%d", i);
nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(State));
}
for (int i = 0; i < NUM_TOXES; ++i) {
tox_node_bootstrap(nodes[i], nodes[(i + 1) % NUM_TOXES]);
tox_node_bootstrap(nodes[(i + 1) % NUM_TOXES], nodes[i]);
}
// Generate friend pairs
for (int i = 0; i < NUM_FRIEND_PAIRS; ++i) {
int t1, t2;
do {
t1 = rand() % NUM_TOXES;
t2 = rand() % NUM_TOXES;
} while (t1 == t2);
uint8_t addr[TOX_ADDRESS_SIZE];
tox_self_get_address(tox_node_get_tox(nodes[t1]), addr);
Tox_Err_Friend_Add err;
tox_friend_add(tox_node_get_tox(nodes[t2]), addr, (const uint8_t *)FR_MESSAGE, 6, &err);
if (err == TOX_ERR_FRIEND_ADD_OK) {
states[t1].friend_count++;
states[t2].friend_count++;
// Bootstrap off each other
tox_node_bootstrap(nodes[t2], nodes[t1]);
tox_node_bootstrap(nodes[t1], nodes[t2]);
}
}
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}
#undef FR_MESSAGE
#undef NUM_TOXES
#undef NUM_FRIEND_PAIRS

View File

@@ -0,0 +1,205 @@
#include "framework/framework.h"
#include "../../toxav/toxav.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
bool incoming;
uint32_t state;
} CallState;
#define WAIT_UNTIL_AV(av, cond) do { \
while(!(cond) && tox_scenario_is_running(self)) { \
toxav_iterate(av); \
tox_scenario_yield(self); \
} \
} while(0)
static void on_call(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
CallState *state = (CallState *)tox_node_get_script_ctx(self);
tox_node_log(self, "Received call from friend %u", friend_number);
state->incoming = true;
}
static void on_call_state(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
CallState *cs = (CallState *)tox_node_get_script_ctx(self);
tox_node_log(self, "Call state changed to %u", state);
cs->state = state;
}
static void on_audio_receive(ToxAV *av, uint32_t friend_number, int16_t const *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate, void *user_data)
{
(void)av;
(void)friend_number;
(void)pcm;
(void)sample_count;
(void)channels;
(void)sampling_rate;
(void)user_data;
}
static void on_video_receive(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height,
uint8_t const *y, uint8_t const *u, uint8_t const *v,
int32_t ystride, int32_t ustride, int32_t vstride, void *user_data)
{
(void)av;
(void)friend_number;
(void)width;
(void)height;
(void)y;
(void)u;
(void)v;
(void)ystride;
(void)ustride;
(void)vstride;
(void)user_data;
}
static void alice_script(ToxNode *self, void *ctx)
{
CallState *state = (CallState *)ctx;
Tox *tox = tox_node_get_tox(self);
Toxav_Err_New av_err;
ToxAV *av = toxav_new(tox, &av_err);
ck_assert(av_err == TOXAV_ERR_NEW_OK);
toxav_callback_call(av, on_call, self);
toxav_callback_call_state(av, on_call_state, self);
toxav_callback_audio_receive_frame(av, on_audio_receive, self);
toxav_callback_video_receive_frame(av, on_video_receive, self);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
// 1. Regular AV call - Alice calls, Bob answers, Bob hangs up
tox_node_log(self, "--- Starting Regular AV Call ---");
state->state = 0;
Toxav_Err_Call call_err;
toxav_call(av, 0, 48, 4000, &call_err);
ck_assert(call_err == TOXAV_ERR_CALL_OK);
WAIT_UNTIL_AV(av, state->state & TOXAV_FRIEND_CALL_STATE_FINISHED);
tox_node_log(self, "Regular AV Call finished (state=%u)", state->state);
tox_scenario_barrier_wait(self);
// 2. Reject flow - Alice calls, Bob rejects
tox_node_log(self, "--- Starting Reject Flow ---");
state->state = 0;
toxav_call(av, 0, 48, 0, &call_err);
ck_assert(call_err == TOXAV_ERR_CALL_OK);
WAIT_UNTIL_AV(av, state->state & TOXAV_FRIEND_CALL_STATE_FINISHED);
tox_node_log(self, "Reject Flow finished");
tox_scenario_barrier_wait(self);
// 3. Cancel flow - Alice calls, Alice cancels
tox_node_log(self, "--- Starting Cancel Flow ---");
state->state = 0;
toxav_call(av, 0, 48, 0, &call_err);
ck_assert(call_err == TOXAV_ERR_CALL_OK);
// Wait for Bob to see it ringing
tox_scenario_barrier_wait(self);
tox_node_log(self, "Alice: Canceling call...");
Toxav_Err_Call_Control cc_err;
toxav_call_control(av, 0, TOXAV_CALL_CONTROL_CANCEL, &cc_err);
ck_assert(cc_err == TOXAV_ERR_CALL_CONTROL_OK);
// Alice doesn't receive FINISHED state when SHE cancels
tox_node_log(self, "Alice: Cancel Flow finished");
tox_scenario_barrier_wait(self);
toxav_kill(av);
}
static void bob_script(ToxNode *self, void *ctx)
{
CallState *state = (CallState *)ctx;
Tox *tox = tox_node_get_tox(self);
Toxav_Err_New av_err;
ToxAV *av = toxav_new(tox, &av_err);
ck_assert(av_err == TOXAV_ERR_NEW_OK);
toxav_callback_call(av, on_call, self);
toxav_callback_call_state(av, on_call_state, self);
toxav_callback_audio_receive_frame(av, on_audio_receive, self);
toxav_callback_video_receive_frame(av, on_video_receive, self);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
// 1. Regular AV call - Bob answers, then hangs up
WAIT_UNTIL_AV(av, state->incoming);
state->incoming = false;
Toxav_Err_Answer answer_err;
toxav_answer(av, 0, 48, 4000, &answer_err);
ck_assert(answer_err == TOXAV_ERR_ANSWER_OK);
// Wait a bit and hang up
for (int i = 0; i < 10; i++) {
toxav_iterate(av);
tox_scenario_yield(self);
}
tox_node_log(self, "Bob: Hanging up...");
Toxav_Err_Call_Control cc_err;
toxav_call_control(av, 0, TOXAV_CALL_CONTROL_CANCEL, &cc_err);
ck_assert(cc_err == TOXAV_ERR_CALL_CONTROL_OK);
tox_scenario_barrier_wait(self);
// 2. Reject flow - Bob rejects
WAIT_UNTIL_AV(av, state->incoming);
state->incoming = false;
tox_node_log(self, "Bob: Rejecting call...");
toxav_call_control(av, 0, TOXAV_CALL_CONTROL_CANCEL, &cc_err);
ck_assert(cc_err == TOXAV_ERR_CALL_CONTROL_OK);
tox_scenario_barrier_wait(self);
// 3. Cancel flow - Alice cancels
WAIT_UNTIL_AV(av, state->incoming);
state->incoming = false;
tox_scenario_barrier_wait(self); // Alice will now cancel
WAIT_UNTIL_AV(av, state->state & TOXAV_FRIEND_CALL_STATE_FINISHED);
tox_node_log(self, "Bob: Cancel Flow finished (Alice canceled)");
tox_scenario_barrier_wait(self);
toxav_kill(av);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
CallState alice_state = {0};
CallState bob_state = {0};
Tox_Options *opts = tox_options_new(nullptr);
tox_options_set_ipv6_enabled(opts, false);
tox_options_set_local_discovery_enabled(opts, false);
ToxNode *alice = tox_scenario_add_node_ex(s, "Alice", alice_script, &alice_state, sizeof(CallState), opts);
ToxNode *bob = tox_scenario_add_node_ex(s, "Bob", bob_script, &bob_state, sizeof(CallState), opts);
tox_options_free(opts);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
tox_scenario_free(s);
return (res == TOX_SCENARIO_DONE) ? 0 : 1;
}

View File

@@ -0,0 +1,184 @@
#include "framework/framework.h"
#include "../../toxav/toxav.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_BOBS 3
typedef struct {
bool incoming;
uint32_t state;
} CallState;
static void on_call(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
CallState *states = (CallState *)tox_node_get_script_ctx(self);
tox_node_log(self, "Received call from friend %u", friend_number);
states[friend_number].incoming = true;
}
static void on_call_state(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
CallState *states = (CallState *)tox_node_get_script_ctx(self);
tox_node_log(self, "Call state for friend %u changed to %u", friend_number, state);
states[friend_number].state = state;
}
static void on_audio_receive(ToxAV *av, uint32_t friend_number, int16_t const *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate, void *user_data)
{
}
static void on_video_receive(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height,
uint8_t const *y, uint8_t const *u, uint8_t const *v,
int32_t ystride, int32_t ustride, int32_t vstride, void *user_data)
{
}
static void alice_script(ToxNode *self, void *ctx)
{
CallState *states = (CallState *)ctx;
Tox *tox = tox_node_get_tox(self);
Toxav_Err_New av_err;
ToxAV *av = toxav_new(tox, &av_err);
ck_assert(av_err == TOXAV_ERR_NEW_OK);
toxav_callback_call(av, on_call, self);
toxav_callback_call_state(av, on_call_state, self);
toxav_callback_audio_receive_frame(av, on_audio_receive, self);
toxav_callback_video_receive_frame(av, on_video_receive, self);
WAIT_UNTIL(tox_node_is_self_connected(self));
for (uint32_t i = 0; i < NUM_BOBS; i++) {
WAIT_UNTIL(tox_node_is_friend_connected(self, i));
}
tox_node_log(self, "All Bobs connected. Calling them...");
for (uint32_t i = 0; i < NUM_BOBS; i++) {
Toxav_Err_Call call_err;
toxav_call(av, i, 48, 3000, &call_err);
ck_assert(call_err == TOXAV_ERR_CALL_OK);
}
int16_t pcm[960] = {0};
uint8_t *video_y = (uint8_t *)calloc(800 * 600, sizeof(uint8_t));
uint8_t *video_u = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t));
uint8_t *video_v = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t));
// Send a few frames to verify flow
for (int i = 0; i < 20; i++) {
toxav_iterate(av);
for (uint32_t j = 0; j < NUM_BOBS; j++) {
if (states[j].state & TOXAV_FRIEND_CALL_STATE_SENDING_A) {
toxav_audio_send_frame(av, j, pcm, 960, 1, 48000, nullptr);
}
if (states[j].state & TOXAV_FRIEND_CALL_STATE_SENDING_V) {
toxav_video_send_frame(av, j, 800, 600, video_y, video_u, video_v, nullptr);
}
}
tox_scenario_yield(self);
}
tox_node_log(self, "Hanging up all calls...");
for (uint32_t i = 0; i < NUM_BOBS; i++) {
toxav_call_control(av, i, TOXAV_CALL_CONTROL_CANCEL, nullptr);
}
// Give it a few ticks to send hangup packets
for (int i = 0; i < 5; i++) {
toxav_iterate(av);
tox_scenario_yield(self);
}
free(video_y);
free(video_u);
free(video_v);
toxav_kill(av);
}
static void bob_script(ToxNode *self, void *ctx)
{
CallState *states = (CallState *)ctx;
Tox *tox = tox_node_get_tox(self);
Toxav_Err_New av_err;
ToxAV *av = toxav_new(tox, &av_err);
ck_assert(av_err == TOXAV_ERR_NEW_OK);
toxav_callback_call(av, on_call, self);
toxav_callback_call_state(av, on_call_state, self);
toxav_callback_audio_receive_frame(av, on_audio_receive, self);
toxav_callback_video_receive_frame(av, on_video_receive, self);
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
while (!states[0].incoming && tox_scenario_is_running(self)) {
toxav_iterate(av);
tox_scenario_yield(self);
}
tox_node_log(self, "Answering call...");
Toxav_Err_Answer answer_err;
toxav_answer(av, 0, 8, 500, &answer_err);
ck_assert(answer_err == TOXAV_ERR_ANSWER_OK);
int16_t pcm[960] = {0};
uint8_t *video_y = (uint8_t *)calloc(800 * 600, sizeof(uint8_t));
uint8_t *video_u = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t));
uint8_t *video_v = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t));
while (!(states[0].state & TOXAV_FRIEND_CALL_STATE_FINISHED) && tox_scenario_is_running(self)) {
toxav_iterate(av);
if (states[0].state & TOXAV_FRIEND_CALL_STATE_SENDING_A) {
toxav_audio_send_frame(av, 0, pcm, 960, 1, 48000, nullptr);
}
if (states[0].state & TOXAV_FRIEND_CALL_STATE_SENDING_V) {
toxav_video_send_frame(av, 0, 800, 600, video_y, video_u, video_v, nullptr);
}
tox_scenario_yield(self);
}
tox_node_log(self, "Call finished.");
free(video_y);
free(video_u);
free(video_v);
toxav_kill(av);
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
CallState alice_states[NUM_BOBS] = {0};
Tox_Options *opts = tox_options_new(nullptr);
tox_options_set_ipv6_enabled(opts, false);
tox_options_set_local_discovery_enabled(opts, false);
ToxNode *alice = tox_scenario_add_node_ex(s, "Alice", alice_script, alice_states, sizeof(alice_states), opts);
ToxNode *bobs[NUM_BOBS];
CallState bob_states[NUM_BOBS];
for (int i = 0; i < NUM_BOBS; i++) {
char name[32];
snprintf(name, sizeof(name), "Bob-%d", i);
bob_states[i] = (CallState) {
0
};
bobs[i] = tox_scenario_add_node_ex(s, name, bob_script, &bob_states[i], sizeof(CallState), opts);
tox_node_bootstrap(bobs[i], alice);
tox_node_friend_add(alice, bobs[i]);
tox_node_friend_add(bobs[i], alice);
}
tox_options_free(opts);
ToxScenarioStatus res = tox_scenario_run(s);
tox_scenario_free(s);
return (res == TOX_SCENARIO_DONE) ? 0 : 1;
}

View File

@@ -0,0 +1,54 @@
#include "framework/framework.h"
#include <stdio.h>
static void alice_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Setting typing to true");
tox_self_set_typing(tox_node_get_tox(self), 0, true, nullptr);
// Wait some time
for (int i = 0; i < 10; ++i) {
tox_scenario_yield(self);
}
tox_node_log(self, "Setting typing to false");
tox_self_set_typing(tox_node_get_tox(self), 0, false, nullptr);
}
static void bob_script(ToxNode *self, void *ctx)
{
WAIT_UNTIL(tox_node_is_self_connected(self));
WAIT_UNTIL(tox_node_is_friend_connected(self, 0));
tox_node_log(self, "Waiting for Alice to start typing...");
WAIT_UNTIL(tox_node_friend_typing_is(self, 0, true));
tox_node_log(self, "Alice is typing!");
tox_node_log(self, "Waiting for Alice to stop typing...");
WAIT_UNTIL(tox_node_friend_typing_is(self, 0, false));
tox_node_log(self, "Alice stopped typing.");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0);
tox_node_bootstrap(bob, alice);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
tox_scenario_log(s, "Test failed with status %u", res);
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -0,0 +1,102 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
Tox_User_Status last_status;
bool status_changed;
} BobState;
static void on_friend_status(const Tox_Event_Friend_Status *event, void *user_data)
{
ToxNode *self = (ToxNode *)user_data;
BobState *state = (BobState *)tox_node_get_script_ctx(self);
if (tox_event_friend_status_get_friend_number(event) == 0) {
state->last_status = tox_event_friend_status_get_status(event);
state->status_changed = true;
tox_node_log(self, "Alice changed status to %s", tox_user_status_to_string(state->last_status));
}
}
static void alice_script(ToxNode *self, void *ctx)
{
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
Tox *tox = tox_node_get_tox(self);
tox_node_log(self, "Setting status to AWAY");
tox_self_set_status(tox, TOX_USER_STATUS_AWAY);
ck_assert(tox_self_get_status(tox) == TOX_USER_STATUS_AWAY);
// Yield to let the status propagate
for (int i = 0; i < 20; ++i) {
tox_scenario_yield(self);
}
tox_node_log(self, "Setting status to BUSY");
tox_self_set_status(tox, TOX_USER_STATUS_BUSY);
ck_assert(tox_self_get_status(tox) == TOX_USER_STATUS_BUSY);
for (int i = 0; i < 20; ++i) {
tox_scenario_yield(self);
}
tox_node_log(self, "Setting status back to NONE");
tox_self_set_status(tox, TOX_USER_STATUS_NONE);
ck_assert(tox_self_get_status(tox) == TOX_USER_STATUS_NONE);
ToxNode *bob = tox_scenario_get_node(tox_node_get_scenario(self), 1);
WAIT_UNTIL(tox_node_is_finished(bob));
}
static void bob_script(ToxNode *self, void *ctx)
{
BobState *state = (BobState *)ctx;
tox_events_callback_friend_status(tox_node_get_dispatch(self), on_friend_status);
tox_node_wait_for_self_connected(self);
tox_node_wait_for_friend_connected(self, 0);
tox_node_log(self, "Waiting for Alice to become AWAY...");
WAIT_UNTIL(state->status_changed && state->last_status == TOX_USER_STATUS_AWAY);
state->status_changed = false;
tox_node_log(self, "Waiting for Alice to become BUSY...");
WAIT_UNTIL(state->status_changed && state->last_status == TOX_USER_STATUS_BUSY);
state->status_changed = false;
tox_node_log(self, "Waiting for Alice to become NONE...");
WAIT_UNTIL(state->status_changed && state->last_status == TOX_USER_STATUS_NONE);
tox_node_log(self, "Status propagation verified!");
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
BobState bob_state = {TOX_USER_STATUS_NONE, false};
tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0);
tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState));
ToxNode *alice = tox_scenario_get_node(s, 0);
ToxNode *bob = tox_scenario_get_node(s, 1);
tox_node_bootstrap(alice, bob);
tox_node_friend_add(alice, bob);
tox_node_friend_add(bob, alice);
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
fprintf(stderr, "Scenario failed with status %u\n", res);
return 1;
}
tox_scenario_free(s);
return 0;
}