Coverage Report

Created: 2026-10-24 08:16

/work/auto_tests/scenarios/framework/framework.c
Line
Count
Source (jump to first uncovered line)
1
#include "framework.h"
2
3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <string.h>
6
#include <pthread.h>
7
#include <time.h>
8
#include <stdarg.h>
9
10
#include "../../../testing/misc_tools.h"
11
#include "../../../toxcore/tox_struct.h"
12
#include "../../../toxcore/network.h"
13
14
335
#define MAX_NODES 128
15
16
typedef struct {
17
    Tox_Connection connection_status;
18
    bool finished;
19
    bool offline;
20
    uint8_t public_key[TOX_PUBLIC_KEY_SIZE];
21
    uint8_t dht_id[TOX_PUBLIC_KEY_SIZE];
22
    uint8_t address[TOX_ADDRESS_SIZE];
23
    uint16_t udp_port;
24
} ToxNodeMirror;
25
26
struct ToxNode {
27
    Tox *tox;
28
    Tox_Options *options;
29
    Tox_Dispatch *dispatch;
30
    uint32_t index;
31
    char *alias;
32
33
    ToxScenario *scenario;
34
    pthread_t thread;
35
    tox_node_script_cb *script;
36
    void *script_ctx;
37
    size_t script_ctx_size;
38
39
    void *mirrored_ctx;
40
    void *mirrored_ctx_public;
41
    ToxNodeMirror mirror;
42
    ToxNodeMirror mirror_public;
43
44
    bool finished;
45
    bool offline;
46
    uint32_t barrier_index;
47
    uint64_t last_tick;
48
    bool seen_events[256];
49
};
50
51
struct ToxScenario {
52
    ToxNode *nodes[MAX_NODES];
53
    uint32_t num_nodes;
54
    uint32_t num_active;
55
    uint32_t num_ready;
56
57
    uint64_t virtual_clock;
58
    uint64_t timeout_ms;
59
    uint64_t tick_count;
60
61
    pthread_mutex_t mutex;
62
    pthread_mutex_t clock_mutex;
63
    pthread_cond_t cond_runner;
64
    pthread_cond_t cond_nodes;
65
66
    bool run_started;
67
68
    bool trace_enabled;
69
    bool event_log_enabled;
70
71
    struct {
72
        const char *name;
73
        uint32_t count;
74
    } barrier;
75
};
76
77
static uint64_t get_scenario_clock(void *user_data)
78
424k
{
79
424k
    ToxScenario *s = (ToxScenario *)user_data;
80
424k
    pthread_mutex_lock(&s->clock_mutex);
81
424k
    uint64_t time = s->virtual_clock;
82
424k
    pthread_mutex_unlock(&s->clock_mutex);
83
424k
    return time;
84
424k
}
85
86
static void framework_debug_log(Tox *tox, Tox_Log_Level level, const char *file, uint32_t line,
87
                                const char *func, const char *message, void *user_data)
88
1.25M
{
89
1.25M
    ToxNode *node = (ToxNode *)user_data;
90
1.25M
    ck_assert(node != nullptr);
91
92
1.25M
    if (level == TOX_LOG_LEVEL_TRACE) {
93
1.24M
        if (node == nullptr || !node->scenario->trace_enabled) {
94
1.24M
            return;
95
1.24M
        }
96
1.24M
    }
97
98
8.16k
    const char *level_name = "UNKNOWN";
99
8.16k
    switch (level) {
100
0
        case TOX_LOG_LEVEL_TRACE:
101
0
            level_name = "TRACE";
102
0
            break;
103
1.31k
        case TOX_LOG_LEVEL_DEBUG:
104
1.31k
            level_name = "DEBUG";
105
1.31k
            break;
106
15
        case TOX_LOG_LEVEL_INFO:
107
15
            level_name = "INFO";
108
15
            break;
109
7.24k
        case TOX_LOG_LEVEL_WARNING:
110
7.24k
            level_name = "WARN";
111
7.24k
            break;
112
0
        case TOX_LOG_LEVEL_ERROR:
113
0
            level_name = "ERROR";
114
0
            break;
115
8.16k
    }
116
117
8.57k
    const uint64_t relative_time = node ? (get_scenario_clock(node->scenario) - 1000) : 0;
118
8.57k
    fprintf(stderr, "[%08lu] [%s] %s %s:%u %s: %s\n", (unsigned long)relative_time,
119
8.57k
            node != nullptr ? node->alias : "Unknown", level_name, file, line, func, message);
120
8.57k
}
121
122
void tox_node_log(ToxNode *node, const char *format, ...)
123
1.68k
{
124
1.68k
    ck_assert(node != nullptr);
125
1.68k
    va_list args;
126
1.68k
    va_start(args, format);
127
1.68k
    uint64_t relative_time = get_scenario_clock(node->scenario) - 1000;
128
1.68k
    fprintf(stderr, "[%08lu] [%s] ", (unsigned long)relative_time, node->alias ? node->alias : "Unknown");
129
1.68k
    vfprintf(stderr, format, args);
130
1.68k
    fprintf(stderr, "\n");
131
1.68k
    va_end(args);
132
1.68k
}
133
134
void tox_scenario_log(const ToxScenario *s, const char *format, ...)
135
178
{
136
178
    va_list args;
137
178
    va_start(args, format);
138
178
    uint64_t relative_time = get_scenario_clock((void *)(uintptr_t)s) - 1000;
139
178
    fprintf(stderr, "[%08lu] [Runner] ", (unsigned long)relative_time);
140
178
    vfprintf(stderr, format, args);
141
178
    fprintf(stderr, "\n");
142
178
    va_end(args);
143
178
}
144
145
ToxScenario *tox_scenario_new(int argc, char *const argv[], uint64_t timeout_ms)
146
55
{
147
55
    static bool seeded = false;
148
55
    if (!seeded) {
149
53
        srand(time(nullptr));
150
53
        seeded = true;
151
53
    }
152
153
55
    for (int i = 1; i < argc; ++i) {
154
0
        if (strcmp(argv[i], "--wait-time") == 0 && i + 1 < argc) {
155
0
            timeout_ms = (uint64_t)atoll(argv[i + 1]) * 1000;
156
0
            break;
157
0
        }
158
0
    }
159
160
55
    ToxScenario *s = (ToxScenario *)calloc(1, sizeof(ToxScenario));
161
55
    ck_assert(s != nullptr);
162
163
55
    pthread_mutex_init(&s->mutex, nullptr);
164
55
    pthread_mutex_init(&s->clock_mutex, nullptr);
165
55
    pthread_cond_init(&s->cond_runner, nullptr);
166
55
    pthread_cond_init(&s->cond_nodes, nullptr);
167
168
55
    s->timeout_ms = timeout_ms;
169
55
    s->virtual_clock = 1000;
170
55
    s->trace_enabled = (getenv("TOX_TRACE") != nullptr);
171
55
    s->event_log_enabled = (getenv("TOX_EVENT_LOG") != nullptr);
172
55
    return s;
173
55
}
174
175
void tox_scenario_free(ToxScenario *s)
176
55
{
177
390
    for (uint32_t i = 0; i < s->num_nodes; ++i) {
178
335
        ToxNode *node = s->nodes[i];
179
335
        ck_assert(node != nullptr);
180
335
        tox_dispatch_free(node->dispatch);
181
335
        tox_kill(node->tox);
182
335
        tox_options_free(node->options);
183
335
        free(node->alias);
184
335
        free(node->mirrored_ctx);
185
335
        free(node->mirrored_ctx_public);
186
335
        free(node);
187
335
    }
188
55
    pthread_mutex_destroy(&s->mutex);
189
55
    pthread_mutex_destroy(&s->clock_mutex);
190
55
    pthread_cond_destroy(&s->cond_runner);
191
55
    pthread_cond_destroy(&s->cond_nodes);
192
55
    free(s);
193
55
}
194
195
Tox *tox_node_get_tox(const ToxNode *node)
196
56.7k
{
197
56.7k
    ck_assert(node != nullptr);
198
56.7k
    ToxScenario *s = node->scenario;
199
56.7k
    pthread_mutex_lock(&s->mutex);
200
56.7k
    Tox *tox = node->tox;
201
56.7k
    pthread_mutex_unlock(&s->mutex);
202
56.7k
    return tox;
203
56.7k
}
204
205
void tox_node_get_address(const ToxNode *node, uint8_t *address)
206
37
{
207
37
    ck_assert(node != nullptr);
208
37
    ck_assert(address != nullptr);
209
37
    memcpy(address, node->mirror_public.address, TOX_ADDRESS_SIZE);
210
37
}
211
212
ToxScenario *tox_node_get_scenario(const ToxNode *node)
213
751
{
214
751
    ck_assert(node != nullptr);
215
751
    return node->scenario;
216
751
}
217
218
Tox_Connection tox_node_get_connection_status(const ToxNode *node)
219
50.3k
{
220
50.3k
    ck_assert(node != nullptr);
221
50.3k
    return node->mirror_public.connection_status;
222
50.3k
}
223
224
Tox_Connection tox_node_get_friend_connection_status(const ToxNode *node, uint32_t friend_number)
225
77.7k
{
226
77.7k
    ck_assert(node != nullptr);
227
    // Note: Friend status is not currently mirrored.
228
    // This is safe if called by the node on itself.
229
    // If called on a peer, it might still race.
230
77.7k
    Tox_Err_Friend_Query err;
231
77.7k
    Tox_Connection conn = tox_friend_get_connection_status(node->tox, friend_number, &err);
232
77.7k
    if (err != TOX_ERR_FRIEND_QUERY_OK) {
233
99
        return TOX_CONNECTION_NONE;
234
99
    }
235
77.6k
    return conn;
236
77.7k
}
237
238
bool tox_node_is_self_connected(const ToxNode *node)
239
50.2k
{
240
50.2k
    return tox_node_get_connection_status(node) != TOX_CONNECTION_NONE;
241
50.2k
}
242
243
bool tox_node_is_friend_connected(const ToxNode *node, uint32_t friend_number)
244
12.5k
{
245
12.5k
    return tox_node_get_friend_connection_status(node, friend_number) != TOX_CONNECTION_NONE;
246
12.5k
}
247
248
uint32_t tox_node_get_conference_peer_count(const ToxNode *node, uint32_t conference_number)
249
22
{
250
22
    ck_assert(node != nullptr);
251
22
    Tox_Err_Conference_Peer_Query err;
252
22
    uint32_t count = tox_conference_peer_count(node->tox, conference_number, &err);
253
22
    if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
254
0
        return 0;
255
0
    }
256
22
    return count;
257
22
}
258
259
void tox_node_set_offline(ToxNode *node, bool offline)
260
6
{
261
6
    ck_assert(node != nullptr);
262
6
    node->offline = offline;
263
6
}
264
265
bool tox_node_is_offline(const ToxNode *node)
266
112k
{
267
112k
    ck_assert(node != nullptr);
268
112k
    return node->mirror_public.offline;
269
112k
}
270
271
bool tox_node_is_finished(const ToxNode *node)
272
4.12k
{
273
4.12k
    ck_assert(node != nullptr);
274
4.12k
    ToxScenario *s = node->scenario;
275
4.12k
    pthread_mutex_lock(&s->mutex);
276
4.12k
    bool finished = node->mirror_public.finished;
277
4.12k
    pthread_mutex_unlock(&s->mutex);
278
4.12k
    return finished;
279
4.12k
}
280
281
bool tox_scenario_is_running(ToxNode *self)
282
83.6k
{
283
83.6k
    bool running;
284
83.6k
    pthread_mutex_lock(&self->scenario->mutex);
285
83.6k
    running = self->scenario->run_started;
286
83.6k
    pthread_mutex_unlock(&self->scenario->mutex);
287
83.6k
    return running;
288
83.6k
}
289
290
bool tox_node_friend_name_is(const ToxNode *node, uint32_t friend_number, const uint8_t *name, size_t length)
291
5
{
292
5
    ck_assert(node != nullptr);
293
5
    Tox_Err_Friend_Query err;
294
5
    size_t actual_len = tox_friend_get_name_size(node->tox, friend_number, &err);
295
5
    if (err != TOX_ERR_FRIEND_QUERY_OK || actual_len != length) {
296
2
        return false;
297
2
    }
298
3
    uint8_t *actual_name = (uint8_t *)malloc(length);
299
3
    ck_assert(actual_name != nullptr);
300
3
    tox_friend_get_name(node->tox, friend_number, actual_name, nullptr);
301
3
    bool match = (memcmp(actual_name, name, length) == 0);
302
3
    free(actual_name);
303
3
    return match;
304
3
}
305
306
bool tox_node_friend_status_message_is(const ToxNode *node, uint32_t friend_number, const uint8_t *msg, size_t length)
307
6
{
308
6
    ck_assert(node != nullptr);
309
6
    Tox_Err_Friend_Query err;
310
6
    size_t actual_len = tox_friend_get_status_message_size(node->tox, friend_number, &err);
311
6
    if (err != TOX_ERR_FRIEND_QUERY_OK || actual_len != length) {
312
3
        return false;
313
3
    }
314
3
    uint8_t *actual_msg = (uint8_t *)malloc(length);
315
3
    ck_assert(actual_msg != nullptr);
316
3
    tox_friend_get_status_message(node->tox, friend_number, actual_msg, nullptr);
317
3
    bool match = (memcmp(actual_msg, msg, length) == 0);
318
3
    free(actual_msg);
319
3
    return match;
320
3
}
321
322
bool tox_node_friend_typing_is(const ToxNode *node, uint32_t friend_number, bool is_typing)
323
14
{
324
14
    ck_assert(node != nullptr);
325
14
    Tox_Err_Friend_Query err;
326
14
    bool actual = tox_friend_get_typing(node->tox, friend_number, &err);
327
14
    return (err == TOX_ERR_FRIEND_QUERY_OK && actual == is_typing);
328
14
}
329
330
void tox_scenario_yield(ToxNode *self)
331
112k
{
332
    // 1. Poll Tox (Strictly in the node's thread), only if not offline
333
112k
    if (!tox_node_is_offline(self)) {
334
110k
        Tox_Err_Events_Iterate ev_err;
335
110k
        Tox_Events *events = tox_events_iterate(self->tox, nullptr, &ev_err);
336
110k
        if (events != nullptr) {
337
5.33k
            uint32_t size = tox_events_get_size(events);
338
351k
            for (uint32_t j = 0; j < size; ++j) {
339
345k
                const Tox_Event *ev = tox_events_get(events, j);
340
345k
                const Tox_Event_Type type = tox_event_get_type(ev);
341
345k
                self->seen_events[type] = true;
342
345k
                if (self->scenario->event_log_enabled) {
343
0
                    tox_node_log(self, "Received event: %s (%u)", tox_event_type_to_string(type), type);
344
0
                }
345
345k
            }
346
5.33k
            tox_dispatch_invoke(self->dispatch, events, self);
347
5.33k
            tox_events_free(events);
348
5.33k
        }
349
110k
    }
350
351
112k
    ToxScenario *s = self->scenario;
352
353
    // 2. Update mirror (Outside lock to reduce contention)
354
112k
    self->mirror.connection_status = tox_self_get_connection_status(self->tox);
355
112k
    self->mirror.offline = self->offline;
356
112k
    tox_self_get_public_key(self->tox, self->mirror.public_key);
357
112k
    tox_self_get_dht_id(self->tox, self->mirror.dht_id);
358
112k
    tox_self_get_address(self->tox, self->mirror.address);
359
112k
    Tox_Err_Get_Port port_err;
360
112k
    self->mirror.udp_port = tox_self_get_udp_port(self->tox, &port_err);
361
362
112k
    if (self->mirrored_ctx != nullptr && self->script_ctx != nullptr) {
363
90.0k
        memcpy(self->mirrored_ctx, self->script_ctx, self->script_ctx_size);
364
90.0k
    }
365
366
112k
    pthread_mutex_lock(&s->mutex);
367
368
112k
    self->mirror.finished = self->finished;
369
112k
    self->last_tick = s->tick_count;
370
112k
    s->num_ready++;
371
372
    // Wake runner
373
112k
    pthread_cond_signal(&s->cond_runner);
374
375
    // Wait for next tick
376
225k
    while (self->last_tick == s->tick_count && s->run_started) {
377
112k
        pthread_cond_wait(&s->cond_nodes, &s->mutex);
378
112k
    }
379
380
112k
    pthread_mutex_unlock(&s->mutex);
381
382
    // Give the OS a chance to deliver UDP packets
383
112k
    c_sleep(1);
384
112k
}
385
386
void tox_scenario_wait_for_event(ToxNode *self, Tox_Event_Type type)
387
7
{
388
141
    while (!self->seen_events[type] && tox_scenario_is_running(self)) {
389
134
        tox_scenario_yield(self);
390
134
    }
391
7
}
392
393
void tox_scenario_barrier_wait(ToxNode *self)
394
246
{
395
246
    ToxScenario *s = self->scenario;
396
246
    pthread_mutex_lock(&s->mutex);
397
246
    uint32_t my_barrier_index = ++self->barrier_index;
398
246
    pthread_mutex_unlock(&s->mutex);
399
400
8.02k
    while (tox_scenario_is_running(self)) {
401
        // Even if all nodes have reached the barrier, we MUST yield at least once
402
        // to ensure the runner has a chance to synchronize the snapshots for this barrier.
403
8.02k
        tox_scenario_yield(self);
404
405
8.02k
        pthread_mutex_lock(&s->mutex);
406
8.02k
        uint32_t reached = 0;
407
47.5k
        for (uint32_t i = 0; i < s->num_nodes; ++i) {
408
39.4k
            if (s->nodes[i]->barrier_index >= my_barrier_index || s->nodes[i]->finished) {
409
27.4k
                reached++;
410
27.4k
            }
411
39.4k
        }
412
8.02k
        const bool done = reached >= s->num_nodes;
413
8.02k
        pthread_mutex_unlock(&s->mutex);
414
415
8.02k
        if (done) {
416
246
            break;
417
246
        }
418
8.02k
    }
419
246
}
420
421
void tox_node_wait_for_self_connected(ToxNode *self)
422
194
{
423
194
    WAIT_UNTIL(tox_node_is_self_connected(self));
424
194
}
425
426
void tox_node_wait_for_friend_connected(ToxNode *self, uint32_t friend_number)
427
36
{
428
36
    WAIT_UNTIL(tox_node_is_friend_connected(self, friend_number));
429
36
}
430
431
static void *node_thread_wrapper(void *arg)
432
335
{
433
335
    ToxNode *node = (ToxNode *)arg;
434
335
    ck_assert(node != nullptr);
435
335
    ToxScenario *s = node->scenario;
436
437
    // Wait for the starting signal from runner
438
335
    pthread_mutex_lock(&s->mutex);
439
335
    s->num_ready++;
440
335
    pthread_cond_signal(&s->cond_runner);
441
670
    while (s->tick_count == 0 && s->run_started) {
442
335
        pthread_cond_wait(&s->cond_nodes, &s->mutex);
443
335
    }
444
335
    pthread_mutex_unlock(&s->mutex);
445
446
    // Run the actual script
447
335
    srand(time(nullptr) + node->index);
448
335
    node->script(node, node->script_ctx);
449
450
    // After the script is done, keep polling until the scenario is over
451
335
    pthread_mutex_lock(&s->mutex);
452
335
    node->finished = true;
453
335
    s->num_active--;
454
335
    uint32_t active = s->num_active;
455
456
335
    tox_node_log(node, "finished script, active nodes remaining: %u", active);
457
458
335
    pthread_cond_signal(&s->cond_runner);
459
460
9.06k
    while (s->run_started) {
461
8.73k
        uint64_t tick_at_start = s->tick_count;
462
8.73k
        s->num_ready++;
463
8.73k
        pthread_cond_signal(&s->cond_runner);
464
465
17.4k
        while (tick_at_start == s->tick_count && s->run_started) {
466
8.73k
            pthread_cond_wait(&s->cond_nodes, &s->mutex);
467
8.73k
        }
468
8.73k
        pthread_mutex_unlock(&s->mutex);
469
470
        // 2. Update mirror outside lock
471
8.73k
        node->mirror.connection_status = tox_self_get_connection_status(node->tox);
472
8.73k
        node->mirror.finished = node->finished;
473
8.73k
        node->mirror.offline = node->offline;
474
475
8.73k
        if (node->mirrored_ctx != nullptr && node->script_ctx != nullptr) {
476
8.01k
            memcpy(node->mirrored_ctx, node->script_ctx, node->script_ctx_size);
477
8.01k
        }
478
479
        // Poll Tox even when script is "finished"
480
8.73k
        Tox_Err_Events_Iterate ev_err;
481
8.73k
        Tox_Events *events = tox_events_iterate(node->tox, nullptr, &ev_err);
482
8.73k
        if (events != nullptr) {
483
276
            tox_dispatch_invoke(node->dispatch, events, node);
484
276
            tox_events_free(events);
485
276
        }
486
8.73k
        pthread_mutex_lock(&s->mutex);
487
8.73k
    }
488
335
    pthread_mutex_unlock(&s->mutex);
489
490
335
    return nullptr;
491
335
}
492
493
static Tox *create_tox_with_port_retry(Tox_Options *opts, void *log_user_data, Tox_Err_New *out_err)
494
340
{
495
340
    tox_options_set_log_user_data(opts, log_user_data);
496
497
340
    if (tox_options_get_start_port(opts) == 0 && tox_options_get_end_port(opts) == 0) {
498
335
        tox_options_set_start_port(opts, TOX_PORT_DEFAULT);
499
335
        tox_options_set_end_port(opts, 65535);
500
335
    }
501
502
340
    uint16_t tcp_port = tox_options_get_tcp_port(opts);
503
340
    Tox *tox = nullptr;
504
505
340
    for (int i = 0; i < 50; ++i) {
506
340
        tox = tox_new(opts, out_err);
507
508
340
        if (*out_err != TOX_ERR_NEW_PORT_ALLOC || tcp_port == 0) {
509
340
            break;
510
340
        }
511
512
0
        tcp_port++;
513
0
        tox_options_set_tcp_port(opts, tcp_port);
514
0
    }
515
516
340
    return tox;
517
340
}
518
519
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)
520
335
{
521
335
    if (s->num_nodes >= MAX_NODES) {
522
0
        return nullptr;
523
0
    }
524
525
335
    ToxNode *node = (ToxNode *)calloc(1, sizeof(ToxNode));
526
335
    ck_assert(node != nullptr);
527
335
    if (alias != nullptr) {
528
335
        size_t len = strlen(alias) + 1;
529
335
        node->alias = (char *)malloc(len);
530
335
        ck_assert(node->alias != nullptr);
531
335
        memcpy(node->alias, alias, len);
532
335
    }
533
335
    node->index = s->num_nodes;
534
335
    node->scenario = s;
535
335
    node->script = script;
536
335
    node->script_ctx = ctx;
537
335
    node->script_ctx_size = ctx_size;
538
539
335
    if (ctx_size > 0) {
540
258
        node->mirrored_ctx = calloc(1, ctx_size);
541
258
        ck_assert(node->mirrored_ctx != nullptr);
542
258
        node->mirrored_ctx_public = calloc(1, ctx_size);
543
258
        ck_assert(node->mirrored_ctx_public != nullptr);
544
258
        if (ctx != nullptr) {
545
258
            memcpy(node->mirrored_ctx, ctx, ctx_size);
546
258
            memcpy(node->mirrored_ctx_public, ctx, ctx_size);
547
258
        }
548
258
    }
549
550
335
    Tox_Options *opts = tox_options_new(nullptr);
551
335
    if (options != nullptr) {
552
53
        tox_options_copy(opts, options);
553
282
    } else {
554
282
        tox_options_set_ipv6_enabled(opts, false);
555
282
        tox_options_set_local_discovery_enabled(opts, false);
556
282
    }
557
558
335
    tox_options_set_log_callback(opts, framework_debug_log);
559
335
    Tox_Err_New err;
560
335
    node->tox = create_tox_with_port_retry(opts, node, &err);
561
562
335
    if (err == TOX_ERR_NEW_OK) {
563
335
        ck_assert(node->tox != nullptr);
564
335
        node->options = tox_options_new(nullptr);
565
335
        tox_options_copy(node->options, opts);
566
335
    }
567
568
335
    tox_options_free(opts);
569
570
571
335
    if (err != TOX_ERR_NEW_OK) {
572
0
        tox_scenario_log(s, "Failed to create Tox instance for node %s: %u", alias, err);
573
0
        free(node);
574
0
        return nullptr;
575
0
    }
576
577
335
    node->dispatch = tox_dispatch_new(nullptr);
578
335
    tox_events_init(node->tox);
579
335
    mono_time_set_current_time_callback(node->tox->mono_time, get_scenario_clock, s);
580
581
    // Initial mirror population
582
335
    node->mirror.connection_status = tox_self_get_connection_status(node->tox);
583
335
    tox_self_get_public_key(node->tox, node->mirror.public_key);
584
335
    tox_self_get_dht_id(node->tox, node->mirror.dht_id);
585
335
    tox_self_get_address(node->tox, node->mirror.address);
586
335
    Tox_Err_Get_Port port_err;
587
335
    node->mirror.udp_port = tox_self_get_udp_port(node->tox, &port_err);
588
589
335
    node->mirror_public = node->mirror;
590
591
335
    s->nodes[s->num_nodes++] = node;
592
335
    return node;
593
335
}
594
595
ToxNode *tox_scenario_add_node(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size)
596
282
{
597
282
    return tox_scenario_add_node_ex(s, alias, script, ctx, ctx_size, nullptr);
598
282
}
599
600
ToxNode *tox_scenario_get_node(ToxScenario *s, uint32_t index)
601
3.97k
{
602
3.97k
    if (index >= s->num_nodes) {
603
0
        return nullptr;
604
0
    }
605
3.97k
    return s->nodes[index];
606
3.97k
}
607
608
const char *tox_node_get_alias(const ToxNode *node)
609
1
{
610
1
    ck_assert(node != nullptr);
611
1
    return node->alias;
612
1
}
613
614
uint32_t tox_node_get_index(const ToxNode *node)
615
80.0k
{
616
80.0k
    ck_assert(node != nullptr);
617
80.0k
    return node->index;
618
80.0k
}
619
620
void *tox_node_get_script_ctx(const ToxNode *node)
621
185k
{
622
185k
    ck_assert(node != nullptr);
623
185k
    return node->script_ctx;
624
185k
}
625
626
const void *tox_node_get_peer_ctx(const ToxNode *node)
627
42
{
628
42
    ck_assert(node != nullptr);
629
42
    return node->mirrored_ctx_public;
630
42
}
631
632
Tox_Dispatch *tox_node_get_dispatch(const ToxNode *node)
633
308
{
634
308
    ck_assert(node != nullptr);
635
308
    return node->dispatch;
636
308
}
637
638
uint64_t tox_scenario_get_time(ToxScenario *s)
639
19.1k
{
640
19.1k
    pthread_mutex_lock(&s->clock_mutex);
641
19.1k
    uint64_t time = s->virtual_clock;
642
19.1k
    pthread_mutex_unlock(&s->clock_mutex);
643
19.1k
    return time;
644
19.1k
}
645
646
ToxScenarioStatus tox_scenario_run(ToxScenario *s)
647
55
{
648
55
    pthread_mutex_lock(&s->mutex);
649
55
    s->num_active = s->num_nodes;
650
55
    s->num_ready = 0;
651
55
    s->tick_count = 0;
652
55
    s->run_started = true;
653
55
    pthread_mutex_unlock(&s->mutex);
654
655
    // Start all node threads
656
390
    for (uint32_t i = 0; i < s->num_nodes; ++i) {
657
335
        pthread_create(&s->nodes[i]->thread, nullptr, node_thread_wrapper, s->nodes[i]);
658
335
    }
659
660
55
    pthread_mutex_lock(&s->mutex);
661
662
55
    uint64_t start_clock = tox_scenario_get_time(s);
663
55
    uint64_t deadline = start_clock + s->timeout_ms;
664
665
19.1k
    while (s->num_active > 0 && tox_scenario_get_time(s) < deadline) {
666
        // 1. Wait until all nodes (including finished ones) have reached the barrier
667
96.5k
        while (s->num_ready < s->num_nodes) {
668
77.4k
            pthread_cond_wait(&s->cond_runner, &s->mutex);
669
77.4k
        }
670
671
        // 2. Synchronize Snapshots
672
140k
        for (uint32_t i = 0; i < s->num_nodes; ++i) {
673
121k
            ToxNode *node = s->nodes[i];
674
121k
            ck_assert(node != nullptr);
675
121k
            node->mirror_public = node->mirror;
676
121k
            if (node->mirrored_ctx_public && node->mirrored_ctx) {
677
98.3k
                memcpy(node->mirrored_ctx_public, node->mirrored_ctx, node->script_ctx_size);
678
98.3k
            }
679
121k
        }
680
681
        // 3. Advance tick and clock
682
19.0k
        s->num_ready = 0;
683
19.0k
        s->tick_count++;
684
19.0k
        pthread_mutex_lock(&s->clock_mutex);
685
19.0k
        s->virtual_clock += TOX_SCENARIO_TICK_MS;
686
19.0k
        pthread_mutex_unlock(&s->clock_mutex);
687
688
19.0k
        if (s->tick_count % 100 == 0) {
689
168
            uint64_t tick = s->tick_count;
690
168
            uint32_t active = s->num_active;
691
168
            pthread_mutex_unlock(&s->mutex);
692
168
            tox_scenario_log(s, "Tick %lu, active nodes: %u", (unsigned long)tick, active);
693
168
            pthread_mutex_lock(&s->mutex);
694
168
        }
695
696
        // 4. Release nodes for the new tick
697
19.0k
        pthread_cond_broadcast(&s->cond_nodes);
698
19.0k
    }
699
700
55
    ToxScenarioStatus result = (s->num_active == 0) ? TOX_SCENARIO_DONE : TOX_SCENARIO_TIMEOUT;
701
55
    if (result == TOX_SCENARIO_TIMEOUT) {
702
0
        tox_scenario_log(s, "Scenario TIMEOUT after %lu ms (virtual time)", (unsigned long)(tox_scenario_get_time(s) - start_clock));
703
0
    }
704
705
    // Stop nodes
706
55
    s->run_started = false;
707
55
    pthread_cond_broadcast(&s->cond_nodes);
708
55
    pthread_mutex_unlock(&s->mutex);
709
710
390
    for (uint32_t i = 0; i < s->num_nodes; ++i) {
711
335
        pthread_join(s->nodes[i]->thread, nullptr);
712
335
    }
713
714
55
    return result;
715
55
}
716
717
void tox_node_bootstrap(ToxNode *a, ToxNode *b)
718
568
{
719
568
    uint8_t pk[TOX_PUBLIC_KEY_SIZE];
720
568
    memcpy(pk, b->mirror_public.dht_id, TOX_PUBLIC_KEY_SIZE);
721
568
    const uint16_t port = b->mirror_public.udp_port;
722
723
568
    Tox_Err_Bootstrap err;
724
568
    tox_bootstrap(a->tox, "127.0.0.1", port, pk, &err);
725
568
    if (err != TOX_ERR_BOOTSTRAP_OK) {
726
0
        tox_node_log(a, "Error bootstrapping from %s: %u", b->alias, err);
727
0
    }
728
568
}
729
730
void tox_node_friend_add(ToxNode *a, ToxNode *b)
731
148
{
732
148
    uint8_t pk[TOX_PUBLIC_KEY_SIZE];
733
148
    memcpy(pk, b->mirror_public.public_key, TOX_PUBLIC_KEY_SIZE);
734
148
    Tox_Err_Friend_Add err;
735
148
    tox_friend_add_norequest(a->tox, pk, &err);
736
148
    if (err != TOX_ERR_FRIEND_ADD_OK) {
737
0
        tox_node_log(a, "Error adding friend %s: %u", b->alias, err);
738
0
    }
739
148
}
740
741
void tox_node_reload(ToxNode *node)
742
5
{
743
5
    ck_assert(node != nullptr);
744
5
    ToxScenario *s = node->scenario;
745
5
    pthread_mutex_lock(&s->mutex);
746
747
    // 1. Save data
748
5
    size_t size = tox_get_savedata_size(node->tox);
749
5
    uint8_t *data = (uint8_t *)malloc(size);
750
5
    ck_assert(data != nullptr);
751
5
    tox_get_savedata(node->tox, data);
752
753
5
    Tox *old_tox = node->tox;
754
5
    Tox_Dispatch *old_dispatch = node->dispatch;
755
756
    // Invalidate node state while reloading
757
5
    node->tox = nullptr;
758
5
    node->dispatch = nullptr;
759
760
5
    pthread_mutex_unlock(&s->mutex);
761
762
    // 2. Kill old instance
763
5
    tox_dispatch_free(old_dispatch);
764
5
    tox_kill(old_tox);
765
766
    // 3. Create new instance from save
767
5
    Tox_Options *opts = tox_options_new(nullptr);
768
5
    ck_assert(opts != nullptr);
769
5
    tox_options_copy(opts, node->options);
770
5
    tox_options_set_savedata_type(opts, TOX_SAVEDATA_TYPE_TOX_SAVE);
771
5
    tox_options_set_savedata_data(opts, data, size);
772
773
5
    Tox_Err_New err;
774
5
    Tox *new_tox = create_tox_with_port_retry(opts, node, &err);
775
5
    tox_options_free(opts);
776
5
    free(data);
777
778
5
    if (err != TOX_ERR_NEW_OK) {
779
0
        tox_node_log(node, "Failed to reload Tox instance: %u", err);
780
0
        return;
781
0
    }
782
783
5
    ck_assert_msg(new_tox != nullptr, "tox_new said OK but returned NULL");
784
5
    Tox_Dispatch *new_dispatch = tox_dispatch_new(nullptr);
785
5
    tox_events_init(new_tox);
786
5
    mono_time_set_current_time_callback(new_tox->mono_time, get_scenario_clock, s);
787
788
5
    pthread_mutex_lock(&s->mutex);
789
5
    node->tox = new_tox;
790
5
    node->dispatch = new_dispatch;
791
5
    pthread_mutex_unlock(&s->mutex);
792
793
    // Re-bootstrap from siblings
794
17
    for (uint32_t i = 0; i < s->num_nodes; ++i) {
795
12
        if (i != node->index) {
796
7
            tox_node_bootstrap(node, s->nodes[i]);
797
7
        }
798
12
    }
799
5
}