Coverage Report

Created: 2024-01-26 01:52

/work/auto_tests/group_sync_test.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Tests syncing capabilities of groups: we attempt to have multiple peers change the
3
 * group state in a number of ways and make sure that all peers end up with the same
4
 * resulting state after a short period.
5
 */
6
7
#include <stdbool.h>
8
#include <stdint.h>
9
#include <string.h>
10
11
#include "auto_test_support.h"
12
13
#include "../toxcore/tox.h"
14
#include "../toxcore/util.h"
15
16
// these should be kept relatively low so integration tests don't always flake out
17
// but they can be increased for local stress testing
18
796
#define NUM_GROUP_TOXES 5
19
2
#define ROLE_SPAM_ITERATIONS 1
20
4
#define TOPIC_SPAM_ITERATIONS 1
21
22
typedef struct Peers {
23
    uint32_t  num_peers;
24
    int64_t   *peer_ids;
25
} Peers;
26
27
typedef struct State {
28
    uint8_t   callback_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
29
    size_t    topic_length;
30
    Peers     *peers;
31
} State;
32
33
static int add_peer(Peers *peers, uint32_t peer_id)
34
20
{
35
20
    const uint32_t new_idx = peers->num_peers;
36
37
20
    int64_t *tmp_list = (int64_t *)realloc(peers->peer_ids, sizeof(int64_t) * (peers->num_peers + 1));
38
39
20
    if (tmp_list == nullptr) {
40
0
        return -1;
41
0
    }
42
43
20
    ++peers->num_peers;
44
45
20
    tmp_list[new_idx] = (int64_t)peer_id;
46
47
20
    peers->peer_ids = tmp_list;
48
49
20
    return 0;
50
20
}
51
52
static int del_peer(Peers *peers, uint32_t peer_id)
53
0
{
54
0
    bool found_peer = false;
55
0
    int64_t i;
56
57
0
    for (i = 0; i < peers->num_peers; ++i) {
58
0
        if (peers->peer_ids[i] == peer_id) {
59
0
            found_peer = true;
60
0
            break;
61
0
        }
62
0
    }
63
64
0
    if (!found_peer) {
65
0
        return -1;
66
0
    }
67
68
0
    --peers->num_peers;
69
70
0
    if (peers->num_peers == 0) {
71
0
        free(peers->peer_ids);
72
0
        peers->peer_ids = nullptr;
73
0
        return 0;
74
0
    }
75
76
0
    if (peers->num_peers != i) {
77
0
        peers->peer_ids[i] = peers->peer_ids[peers->num_peers];
78
0
    }
79
80
0
    peers->peer_ids[peers->num_peers] = -1;
81
82
0
    int64_t *tmp_list = (int64_t *)realloc(peers->peer_ids, sizeof(int64_t) * (peers->num_peers));
83
84
0
    if (tmp_list == nullptr) {
85
0
        return -1;
86
0
    }
87
88
0
    peers->peer_ids = tmp_list;
89
90
0
    return 0;
91
0
}
92
93
static void peers_cleanup(Peers *peers)
94
5
{
95
5
    free(peers->peer_ids);
96
5
    free(peers);
97
5
}
98
99
static void group_peer_join_handler(Tox *tox, const Tox_Event_Group_Peer_Join *event, void *user_data)
100
22
{
101
22
    AutoTox *autotox = (AutoTox *)user_data;
102
22
    ck_assert(autotox != nullptr);
103
104
22
    State *state = (State *)autotox->state;
105
106
22
    const uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event);
107
108
22
    ck_assert(add_peer(state->peers, peer_id) == 0);
109
110
22
}
111
112
static void group_peer_exit_handler(Tox *tox, const Tox_Event_Group_Peer_Exit *event, void *user_data)
113
0
{
114
0
    AutoTox *autotox = (AutoTox *)user_data;
115
0
    ck_assert(autotox != nullptr);
116
117
0
    State *state = (State *)autotox->state;
118
119
0
    const uint32_t peer_id = tox_event_group_peer_exit_get_peer_id(event);
120
121
0
    ck_assert(del_peer(state->peers, peer_id) == 0);
122
123
0
}
124
125
static void group_topic_handler(Tox *tox, const Tox_Event_Group_Topic *event, void *user_data)
126
875
{
127
875
    AutoTox *autotox = (AutoTox *)user_data;
128
875
    ck_assert(autotox != nullptr);
129
130
875
    State *state = (State *)autotox->state;
131
132
875
    const uint8_t *topic = tox_event_group_topic_get_topic(event);
133
875
    const size_t length = tox_event_group_topic_get_topic_length(event);
134
135
875
    ck_assert(length <= TOX_GROUP_MAX_TOPIC_LENGTH);
136
137
875
    memcpy(state->callback_topic, (const char *)topic, length);
138
875
    state->topic_length = length;
139
875
}
140
141
static bool all_peers_connected(const AutoTox *autotoxes, uint32_t groupnumber)
142
7
{
143
21
    for (uint32_t i = 0; i < NUM_GROUP_TOXES; ++i) {
144
        // make sure we got an invite response
145
20
        if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != 4) {
146
6
            return false;
147
6
        }
148
149
        // make sure we're actually connected
150
14
        if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) {
151
0
            return false;
152
0
        }
153
154
14
        const State *state = (const State *)autotoxes[i].state;
155
156
        // make sure all peers are connected to one another
157
14
        if (state->peers->num_peers == NUM_GROUP_TOXES - 1) {
158
0
            return false;
159
0
        }
160
14
    }
161
162
1
    return true;
163
7
}
164
165
static unsigned int get_peer_roles_checksum(const Tox *tox, const State *state, uint32_t groupnumber)
166
773
{
167
773
    Tox_Group_Role role = tox_group_self_get_role(tox, groupnumber, nullptr);
168
773
    unsigned int checksum = (unsigned int)role;
169
170
3.86k
    for (size_t i = 0; i < state->peers->num_peers; ++i) {
171
3.09k
        role = tox_group_peer_get_role(tox, groupnumber, (uint32_t)state->peers->peer_ids[i], nullptr);
172
3.09k
        checksum += (unsigned int)role;
173
3.09k
    }
174
175
773
    return checksum;
176
773
}
177
178
static bool all_peers_see_same_roles(const AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber)
179
180
{
180
180
    const State *state0 = (const State *)autotoxes[0].state;
181
180
    unsigned int expected_checksum = get_peer_roles_checksum(autotoxes[0].tox, state0, groupnumber);
182
183
596
    for (size_t i = 0; i < num_peers; ++i) {
184
593
        const State *state = (const State *)autotoxes[i].state;
185
593
        unsigned int checksum = get_peer_roles_checksum(autotoxes[i].tox, state, groupnumber);
186
187
593
        if (checksum != expected_checksum) {
188
177
            return false;
189
177
        }
190
593
    }
191
192
3
    return true;
193
180
}
194
195
static void role_spam(const Random *rng, AutoTox *autotoxes, uint32_t num_peers, uint32_t num_demoted,
196
                      uint32_t groupnumber)
197
1
{
198
1
    const State *state0 = (const State *)autotoxes[0].state;
199
1
    Tox *tox0 = autotoxes[0].tox;
200
201
2
    for (size_t iters = 0; iters < ROLE_SPAM_ITERATIONS; ++iters) {
202
        // founder randomly promotes or demotes one of the non-mods
203
1
        uint32_t idx = min_u32(random_u32(rng) % num_demoted, state0->peers->num_peers);
204
1
        Tox_Group_Role f_role = random_u32(rng) % 2 == 0 ? TOX_GROUP_ROLE_MODERATOR : TOX_GROUP_ROLE_USER;
205
1
        int64_t peer_id = state0->peers->peer_ids[idx];
206
207
1
        if (peer_id >= 0) {
208
1
            tox_group_mod_set_role(tox0, groupnumber, (uint32_t)peer_id, f_role, nullptr);
209
1
        }
210
211
        // mods randomly promote or demote one of the non-mods
212
5
        for (uint32_t i = 1; i < num_peers; ++i) {
213
4
            const State *state_i = (const State *)autotoxes[i].state;
214
215
16
            for (uint32_t j = num_demoted; j < num_peers; ++j) {
216
12
                if (i >= state_i->peers->num_peers) {
217
3
                    continue;
218
3
                }
219
220
9
                const State *state_j = (const State *)autotoxes[j].state;
221
9
                Tox_Group_Role role = random_u32(rng) % 2 == 0 ? TOX_GROUP_ROLE_USER : TOX_GROUP_ROLE_OBSERVER;
222
9
                peer_id = state_j->peers->peer_ids[i];
223
224
9
                if (peer_id >= 0) {
225
9
                    tox_group_mod_set_role(autotoxes[j].tox, groupnumber, (uint32_t)peer_id, role, nullptr);
226
9
                }
227
9
            }
228
4
        }
229
230
1
        iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL);
231
1
    }
232
233
178
    do {
234
178
        iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL);
235
178
    } while (!all_peers_see_same_roles(autotoxes, num_peers, groupnumber));
236
1
}
237
238
/* All peers attempt to set a unique topic.
239
 *
240
 * Return true if all peers successfully changed the topic.
241
 */
242
static bool set_topic_all_peers(const Random *rng, AutoTox *autotoxes, size_t num_peers, uint32_t groupnumber)
243
109
{
244
547
    for (size_t i = 0; i < num_peers; ++i) {
245
545
        char new_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
246
545
        snprintf(new_topic, sizeof(new_topic), "peer %zu's topic %u", i, random_u32(rng));
247
545
        const size_t length = strlen(new_topic);
248
249
545
        Tox_Err_Group_Topic_Set err;
250
545
        tox_group_set_topic(autotoxes[i].tox, groupnumber, (const uint8_t *)new_topic, length, &err);
251
252
545
        if (err != TOX_ERR_GROUP_TOPIC_SET_OK) {
253
107
            return false;
254
107
        }
255
545
    }
256
257
2
    return true;
258
109
}
259
260
/* Returns true if all peers have the same topic, and the topic from the get_topic API function
261
 * matches the last topic they received in the topic callback.
262
 */
263
static bool all_peers_have_same_topic(const AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber)
264
297
{
265
297
    uint8_t expected_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
266
267
297
    Tox_Err_Group_State_Queries query_err;
268
297
    size_t expected_topic_length = tox_group_get_topic_size(autotoxes[0].tox, groupnumber, &query_err);
269
270
297
    ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK);
271
272
297
    tox_group_get_topic(autotoxes[0].tox, groupnumber, expected_topic, &query_err);
273
274
297
    ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK);
275
276
297
    const State *state0 = (const State *)autotoxes[0].state;
277
278
297
    if (expected_topic_length != state0->topic_length) {
279
0
        return false;
280
0
    }
281
282
297
    if (memcmp(state0->callback_topic, expected_topic, expected_topic_length) != 0) {
283
0
        return false;
284
0
    }
285
286
513
    for (size_t i = 1; i < num_peers; ++i) {
287
510
        size_t topic_length = tox_group_get_topic_size(autotoxes[i].tox, groupnumber, &query_err);
288
289
510
        ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK);
290
291
510
        if (topic_length != expected_topic_length) {
292
0
            return false;
293
0
        }
294
295
510
        uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH];
296
510
        tox_group_get_topic(autotoxes[i].tox, groupnumber, topic, &query_err);
297
298
510
        ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK);
299
300
510
        if (memcmp(expected_topic, (const char *)topic, topic_length) != 0) {
301
294
            return false;
302
294
        }
303
304
216
        const State *state = (const State *)autotoxes[i].state;
305
306
216
        if (topic_length != state->topic_length) {
307
0
            return false;
308
0
        }
309
310
216
        if (memcmp(state->callback_topic, (const char *)topic, topic_length) != 0) {
311
0
            return false;
312
0
        }
313
216
    }
314
315
3
    return true;
316
297
}
317
318
static void topic_spam(const Random *rng, AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber)
319
2
{
320
4
    for (size_t i = 0; i < TOPIC_SPAM_ITERATIONS; ++i) {
321
109
        do {
322
109
            iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL);
323
109
        } while (!set_topic_all_peers(rng, autotoxes, num_peers, groupnumber));
324
2
    }
325
326
2
    fprintf(stderr, "all peers set the topic at the same time\n");
327
328
296
    do {
329
296
        iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL);
330
296
    } while (!all_peers_have_same_topic(autotoxes, num_peers, groupnumber));
331
332
2
    fprintf(stderr, "all peers see the same topic\n");
333
2
}
334
335
static void group_sync_test(AutoTox *autotoxes)
336
1
{
337
1
    ck_assert(NUM_GROUP_TOXES >= 5);
338
1
    const Random *rng = os_random();
339
1
    ck_assert(rng != nullptr);
340
341
6
    for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) {
342
5
        tox_events_callback_group_peer_join(autotoxes[i].dispatch, group_peer_join_handler);
343
5
        tox_events_callback_group_topic(autotoxes[i].dispatch, group_topic_handler);
344
5
        tox_events_callback_group_peer_exit(autotoxes[i].dispatch, group_peer_exit_handler);
345
346
5
        State *state = (State *)autotoxes[i].state;
347
5
        state->peers = (Peers *)calloc(1, sizeof(Peers));
348
349
5
        ck_assert(state->peers != nullptr);
350
5
    }
351
352
1
    Tox *tox0 = autotoxes[0].tox;
353
1
    State *state0 = (State *)autotoxes[0].state;
354
355
1
    Tox_Err_Group_New err_new;
356
1
    uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *) "test", 4,
357
1
                                         (const uint8_t *)"test", 4,  &err_new);
358
359
1
    ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
360
361
1
    fprintf(stderr, "tox0 creats new group and invites all his friends");
362
363
1
    Tox_Err_Group_State_Queries id_err;
364
1
    uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
365
366
1
    tox_group_get_chat_id(tox0, groupnumber, chat_id, &id_err);
367
1
    ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", id_err);
368
369
5
    for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) {
370
4
        Tox_Err_Group_Join join_err;
371
4
        tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err);
372
4
        ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err);
373
4
        iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL);
374
4
    }
375
376
7
    do {
377
7
        iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL);
378
7
    } while (!all_peers_connected(autotoxes, groupnumber));
379
380
1
    fprintf(stderr, "%d peers joined the group\n", NUM_GROUP_TOXES);
381
382
1
    Tox_Err_Group_Founder_Set_Topic_Lock lock_set_err;
383
1
    tox_group_founder_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_DISABLED, &lock_set_err);
384
1
    ck_assert_msg(lock_set_err == TOX_ERR_GROUP_FOUNDER_SET_TOPIC_LOCK_OK, "failed to disable topic lock: %d",
385
1
                  lock_set_err);
386
387
1
    iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL);
388
389
1
    fprintf(stderr, "founder disabled topic lock; all peers try to set the topic\n");
390
391
1
    topic_spam(rng, autotoxes, NUM_GROUP_TOXES, groupnumber);
392
393
1
    iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL);
394
395
1
    tox_group_founder_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_ENABLED, &lock_set_err);
396
1
    ck_assert_msg(lock_set_err == TOX_ERR_GROUP_FOUNDER_SET_TOPIC_LOCK_OK, "failed to enable topic lock: %d",
397
1
                  lock_set_err);
398
399
1
    do {
400
1
        iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL);
401
1
    } while (!all_peers_have_same_topic(autotoxes, NUM_GROUP_TOXES, groupnumber)
402
1
             && !all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber)
403
1
             && state0->peers->num_peers != NUM_GROUP_TOXES - 1);
404
405
1
    Tox_Err_Group_Mod_Set_Role role_err;
406
407
5
    for (size_t i = 0; i < state0->peers->num_peers; ++i) {
408
4
        tox_group_mod_set_role(tox0, groupnumber, (uint32_t)state0->peers->peer_ids[i], TOX_GROUP_ROLE_MODERATOR,
409
4
                               &role_err);
410
4
        ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set moderator. error: %d", role_err);
411
4
    }
412
413
1
    fprintf(stderr, "founder enabled topic lock and set all peers to moderator role\n");
414
415
1
    do {
416
1
        iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL);
417
1
    } while (!all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber));
418
419
1
    topic_spam(rng, autotoxes, NUM_GROUP_TOXES, groupnumber);
420
421
1
    const unsigned int num_demoted = state0->peers->num_peers / 2;
422
423
1
    fprintf(stderr, "founder demoting %u moderators to user\n", num_demoted);
424
425
3
    for (size_t i = 0; i < num_demoted; ++i) {
426
2
        tox_group_mod_set_role(tox0, groupnumber, (uint32_t)state0->peers->peer_ids[i], TOX_GROUP_ROLE_USER,
427
2
                               &role_err);
428
2
        ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set user. error: %d", role_err);
429
2
    }
430
431
1
    do {
432
1
        iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL);
433
1
    } while (!all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber));
434
435
1
    fprintf(stderr, "Remaining moderators spam change non-moderator roles\n");
436
437
1
    role_spam(rng, autotoxes, NUM_GROUP_TOXES, num_demoted, groupnumber);
438
439
1
    fprintf(stderr, "All peers see the same roles\n");
440
441
6
    for (size_t i = 0; i < NUM_GROUP_TOXES; i++) {
442
5
        tox_group_leave(autotoxes[i].tox, groupnumber, nullptr, 0, nullptr);
443
444
5
        State *state = (State *)autotoxes[i].state;
445
5
        peers_cleanup(state->peers);
446
5
    }
447
448
1
    fprintf(stderr, "All tests passed!\n");
449
1
}
450
451
int main(void)
452
721
{
453
721
    setvbuf(stdout, nullptr, _IONBF, 0);
454
455
721
    Run_Auto_Options autotest_opts = default_run_auto_options();
456
721
    autotest_opts.graph = GRAPH_COMPLETE;
457
458
721
    run_auto_test(nullptr, NUM_GROUP_TOXES, group_sync_test, sizeof(State), &autotest_opts);
459
460
721
    return 0;
461
721
}
462
463
#undef NUM_GROUP_TOXES
464
#undef ROLE_SPAM_ITERATIONS
465
#undef TOPIC_SPAM_ITERATIONS