forked from Green-Sky/tomato
Squashed 'external/toxcore/c-toxcore/' changes from 1828c5356..c9cdae001
c9cdae001 fix(toxav): remove extra copy of video frame on encode 4f6d4546b test: Improve the fake network library. a2581e700 refactor(toxcore): generate `Friend_Request` and `Dht_Nodes_Response` 2aaa11770 refactor(toxcore): use Tox_Memory in generated events 5c367452b test(toxcore): fix incorrect mutex in tox_scenario_get_time 8f92e710f perf: Add a timed limit of number of cookie requests. 695b6417a test: Add some more simulated network support. 815ae9ce9 test(toxcore): fix thread-safety in scenario framework 6d85c754e test(toxcore): add unit tests for net_crypto 9c22e79cc test(support): add SimulatedEnvironment for deterministic testing f34fcb195 chore: Update windows Dockerfile to debian stable (trixie). ece0e8980 fix(group_moderation): allow validating unsorted sanction list signatures a4fa754d7 refactor: rename struct Packet to struct Net_Packet d6f330f85 cleanup: Fix some warnings from coverity. e206bffa2 fix(group_chats): fix sync packets reverting topics 0e4715598 test: Add new scenario testing framework. 668291f44 refactor(toxcore): decouple Network_Funcs from sockaddr via IP_Port fc4396cef fix: potential division by zero in toxav and unsafe hex parsing 8e8b352ab refactor: Add nullable annotations to struct members. 7740bb421 refactor: decouple net_crypto from DHT 1936d4296 test: add benchmark for toxav audio and video 46bfdc2df fix: correct printf format specifiers for unsigned integers REVERT: 1828c5356 fix(toxav): remove extra copy of video frame on encode git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: c9cdae001341e701fca980c9bb9febfeb95d2902
This commit is contained in:
29
auto_tests/scenarios/BUILD.bazel
Normal file
29
auto_tests/scenarios/BUILD.bazel
Normal 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"])]
|
||||
99
auto_tests/scenarios/CMakeLists.txt
Normal file
99
auto_tests/scenarios/CMakeLists.txt
Normal 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()
|
||||
79
auto_tests/scenarios/framework/README.md
Normal file
79
auto_tests/scenarios/framework/README.md
Normal 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.
|
||||
800
auto_tests/scenarios/framework/framework.c
Normal file
800
auto_tests/scenarios/framework/framework.c
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
215
auto_tests/scenarios/framework/framework.h
Normal file
215
auto_tests/scenarios/framework/framework.h
Normal 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
|
||||
144
auto_tests/scenarios/scenario_avatar_test.c
Normal file
144
auto_tests/scenarios/scenario_avatar_test.c
Normal 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;
|
||||
}
|
||||
|
||||
38
auto_tests/scenarios/scenario_bootstrap_test.c
Normal file
38
auto_tests/scenarios/scenario_bootstrap_test.c
Normal 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;
|
||||
}
|
||||
132
auto_tests/scenarios/scenario_conference_av_test.c
Normal file
132
auto_tests/scenarios/scenario_conference_av_test.c
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
300
auto_tests/scenarios/scenario_conference_invite_merge_test.c
Normal file
300
auto_tests/scenarios/scenario_conference_invite_merge_test.c
Normal 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;
|
||||
}
|
||||
132
auto_tests/scenarios/scenario_conference_offline_test.c
Normal file
132
auto_tests/scenarios/scenario_conference_offline_test.c
Normal 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;
|
||||
}
|
||||
111
auto_tests/scenarios/scenario_conference_peer_nick_test.c
Normal file
111
auto_tests/scenarios/scenario_conference_peer_nick_test.c
Normal 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;
|
||||
}
|
||||
131
auto_tests/scenarios/scenario_conference_query_test.c
Normal file
131
auto_tests/scenarios/scenario_conference_query_test.c
Normal 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, >_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;
|
||||
}
|
||||
155
auto_tests/scenarios/scenario_conference_simple_test.c
Normal file
155
auto_tests/scenarios/scenario_conference_simple_test.c
Normal 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;
|
||||
}
|
||||
148
auto_tests/scenarios/scenario_conference_test.c
Normal file
148
auto_tests/scenarios/scenario_conference_test.c
Normal 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
|
||||
38
auto_tests/scenarios/scenario_conference_two_test.c
Normal file
38
auto_tests/scenarios/scenario_conference_two_test.c
Normal 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;
|
||||
}
|
||||
195
auto_tests/scenarios/scenario_dht_nodes_response_api_test.c
Normal file
195
auto_tests/scenarios/scenario_dht_nodes_response_api_test.c
Normal 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
|
||||
46
auto_tests/scenarios/scenario_events_test.c
Normal file
46
auto_tests/scenarios/scenario_events_test.c
Normal 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;
|
||||
}
|
||||
81
auto_tests/scenarios/scenario_file_cancel_test.c
Normal file
81
auto_tests/scenarios/scenario_file_cancel_test.c
Normal 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;
|
||||
}
|
||||
134
auto_tests/scenarios/scenario_file_seek_test.c
Normal file
134
auto_tests/scenarios/scenario_file_seek_test.c
Normal 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
|
||||
150
auto_tests/scenarios/scenario_file_transfer_test.c
Normal file
150
auto_tests/scenarios/scenario_file_transfer_test.c
Normal 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
|
||||
46
auto_tests/scenarios/scenario_friend_connection_test.c
Normal file
46
auto_tests/scenarios/scenario_friend_connection_test.c
Normal 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;
|
||||
}
|
||||
103
auto_tests/scenarios/scenario_friend_delete_test.c
Normal file
103
auto_tests/scenarios/scenario_friend_delete_test.c
Normal 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;
|
||||
}
|
||||
72
auto_tests/scenarios/scenario_friend_query_test.c
Normal file
72
auto_tests/scenarios/scenario_friend_query_test.c
Normal 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;
|
||||
}
|
||||
83
auto_tests/scenarios/scenario_friend_read_receipt_test.c
Normal file
83
auto_tests/scenarios/scenario_friend_read_receipt_test.c
Normal 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;
|
||||
}
|
||||
102
auto_tests/scenarios/scenario_friend_request_spam_test.c
Normal file
102
auto_tests/scenarios/scenario_friend_request_spam_test.c
Normal 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
|
||||
78
auto_tests/scenarios/scenario_friend_request_test.c
Normal file
78
auto_tests/scenarios/scenario_friend_request_test.c
Normal 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;
|
||||
}
|
||||
231
auto_tests/scenarios/scenario_group_general_test.c
Normal file
231
auto_tests/scenarios/scenario_group_general_test.c
Normal 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
|
||||
303
auto_tests/scenarios/scenario_group_invite_test.c
Normal file
303
auto_tests/scenarios/scenario_group_invite_test.c
Normal 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
|
||||
214
auto_tests/scenarios/scenario_group_message_test.c
Normal file
214
auto_tests/scenarios/scenario_group_message_test.c
Normal 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
|
||||
367
auto_tests/scenarios/scenario_group_moderation_test.c
Normal file
367
auto_tests/scenarios/scenario_group_moderation_test.c
Normal 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
|
||||
172
auto_tests/scenarios/scenario_group_save_test.c
Normal file
172
auto_tests/scenarios/scenario_group_save_test.c
Normal 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
|
||||
186
auto_tests/scenarios/scenario_group_state_test.c
Normal file
186
auto_tests/scenarios/scenario_group_state_test.c
Normal 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
|
||||
263
auto_tests/scenarios/scenario_group_sync_test.c
Normal file
263
auto_tests/scenarios/scenario_group_sync_test.c
Normal 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
|
||||
234
auto_tests/scenarios/scenario_group_tcp_test.c
Normal file
234
auto_tests/scenarios/scenario_group_tcp_test.c
Normal 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
|
||||
241
auto_tests/scenarios/scenario_group_topic_revert_test.c
Normal file
241
auto_tests/scenarios/scenario_group_topic_revert_test.c
Normal 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
|
||||
310
auto_tests/scenarios/scenario_group_topic_test.c
Normal file
310
auto_tests/scenarios/scenario_group_topic_test.c
Normal 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
|
||||
51
auto_tests/scenarios/scenario_lan_discovery_test.c
Normal file
51
auto_tests/scenarios/scenario_lan_discovery_test.c
Normal 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;
|
||||
}
|
||||
68
auto_tests/scenarios/scenario_lossless_packet_test.c
Normal file
68
auto_tests/scenarios/scenario_lossless_packet_test.c
Normal 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;
|
||||
}
|
||||
52
auto_tests/scenarios/scenario_lossy_packet_test.c
Normal file
52
auto_tests/scenarios/scenario_lossy_packet_test.c
Normal 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;
|
||||
}
|
||||
65
auto_tests/scenarios/scenario_message_test.c
Normal file
65
auto_tests/scenarios/scenario_message_test.c
Normal 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;
|
||||
}
|
||||
61
auto_tests/scenarios/scenario_netprof_test.c
Normal file
61
auto_tests/scenarios/scenario_netprof_test.c
Normal 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;
|
||||
}
|
||||
105
auto_tests/scenarios/scenario_nospam_test.c
Normal file
105
auto_tests/scenarios/scenario_nospam_test.c
Normal 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;
|
||||
}
|
||||
125
auto_tests/scenarios/scenario_overflow_recvq_test.c
Normal file
125
auto_tests/scenarios/scenario_overflow_recvq_test.c
Normal 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
|
||||
63
auto_tests/scenarios/scenario_overflow_sendq_test.c
Normal file
63
auto_tests/scenarios/scenario_overflow_sendq_test.c
Normal 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
|
||||
61
auto_tests/scenarios/scenario_reconnect_test.c
Normal file
61
auto_tests/scenarios/scenario_reconnect_test.c
Normal 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;
|
||||
}
|
||||
93
auto_tests/scenarios/scenario_save_friend_test.c
Normal file
93
auto_tests/scenarios/scenario_save_friend_test.c
Normal 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;
|
||||
}
|
||||
90
auto_tests/scenarios/scenario_save_load_test.c
Normal file
90
auto_tests/scenarios/scenario_save_load_test.c
Normal 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;
|
||||
}
|
||||
92
auto_tests/scenarios/scenario_self_query_test.c
Normal file
92
auto_tests/scenarios/scenario_self_query_test.c
Normal 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;
|
||||
}
|
||||
|
||||
98
auto_tests/scenarios/scenario_send_message_test.c
Normal file
98
auto_tests/scenarios/scenario_send_message_test.c
Normal 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;
|
||||
}
|
||||
45
auto_tests/scenarios/scenario_set_name_test.c
Normal file
45
auto_tests/scenarios/scenario_set_name_test.c
Normal 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;
|
||||
}
|
||||
45
auto_tests/scenarios/scenario_set_status_message_test.c
Normal file
45
auto_tests/scenarios/scenario_set_status_message_test.c
Normal 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;
|
||||
}
|
||||
160
auto_tests/scenarios/scenario_tox_many_tcp_test.c
Normal file
160
auto_tests/scenarios/scenario_tox_many_tcp_test.c
Normal 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
|
||||
102
auto_tests/scenarios/scenario_tox_many_test.c
Normal file
102
auto_tests/scenarios/scenario_tox_many_test.c
Normal 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
|
||||
205
auto_tests/scenarios/scenario_toxav_basic_test.c
Normal file
205
auto_tests/scenarios/scenario_toxav_basic_test.c
Normal 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;
|
||||
}
|
||||
184
auto_tests/scenarios/scenario_toxav_many_test.c
Normal file
184
auto_tests/scenarios/scenario_toxav_many_test.c
Normal 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;
|
||||
}
|
||||
54
auto_tests/scenarios/scenario_typing_test.c
Normal file
54
auto_tests/scenarios/scenario_typing_test.c
Normal 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;
|
||||
}
|
||||
102
auto_tests/scenarios/scenario_user_status_test.c
Normal file
102
auto_tests/scenarios/scenario_user_status_test.c
Normal 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user