Coverage Report

Created: 2026-10-24 08:16

/work/auto_tests/scenarios/scenario_group_topic_test.c
Line
Count
Source (jump to first uncovered line)
1
#include "framework/framework.h"
2
#include <stdio.h>
3
#include <string.h>
4
#include <stdlib.h>
5
6
160
#define NUM_PEERS 3
7
2
#define GROUP_NAME "The Test Chamber"
8
1
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
9
2
#define TOPIC1 "Topic One"
10
2
#define TOPIC2 "Topic Two"
11
12
typedef struct {
13
    uint32_t group_number;
14
    uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
15
    bool chat_id_ready;
16
    bool connected;
17
    uint32_t peer_ids[NUM_PEERS];
18
    uint8_t last_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
19
    size_t last_topic_len;
20
    Tox_Group_Topic_Lock topic_lock;
21
    Tox_Group_Role self_role;
22
} TopicState;
23
24
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
25
13
{
26
13
    ToxNode *self = (ToxNode *)user_data;
27
13
    TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
28
13
    state->connected = true;
29
13
    state->group_number = tox_event_group_self_join_get_group_number(event);
30
13
    state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, nullptr);
31
13
}
32
33
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
34
6
{
35
6
    ToxNode *self = (ToxNode *)user_data;
36
6
    TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
37
6
    uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event);
38
6
    uint32_t group_number = tox_event_group_peer_join_get_group_number(event);
39
40
6
    Tox_Err_Group_Peer_Query q_err;
41
6
    size_t length = tox_group_peer_get_name_size(tox_node_get_tox(self), group_number, peer_id, &q_err);
42
6
    if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK && length > 0) {
43
6
        uint8_t name[TOX_MAX_NAME_LENGTH + 1];
44
6
        tox_group_peer_get_name(tox_node_get_tox(self), group_number, peer_id, name, &q_err);
45
6
        if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK) {
46
6
            name[length] = 0;
47
6
            if (length >= 4 && memcmp(name, "Peer", 4) == 0) {
48
6
                uint32_t idx = (uint32_t)atoi((const char *)name + 4);
49
6
                if (idx < NUM_PEERS) {
50
6
                    state->peer_ids[idx] = peer_id;
51
6
                }
52
6
            }
53
6
        }
54
6
    }
55
6
}
56
57
static void on_group_topic(const Tox_Event_Group_Topic *event, void *user_data)
58
31
{
59
31
    ToxNode *self = (ToxNode *)user_data;
60
31
    TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
61
31
    state->last_topic_len = tox_event_group_topic_get_topic_length(event);
62
31
    memcpy(state->last_topic, tox_event_group_topic_get_topic(event), state->last_topic_len);
63
31
}
64
65
static void on_group_topic_lock(const Tox_Event_Group_Topic_Lock *event, void *user_data)
66
8
{
67
8
    ToxNode *self = (ToxNode *)user_data;
68
8
    TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
69
8
    state->topic_lock = tox_event_group_topic_lock_get_topic_lock(event);
70
8
}
71
72
static void on_group_moderation(const Tox_Event_Group_Moderation *event, void *user_data)
73
6
{
74
6
    ToxNode *self = (ToxNode *)user_data;
75
6
    TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
76
6
    Tox_Err_Group_Self_Query err;
77
6
    state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, &err);
78
6
}
79
80
static void common_init(ToxNode *self, TopicState *state)
81
8
{
82
8
    Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
83
8
    tox_events_callback_group_self_join(dispatch, on_group_self_join);
84
8
    tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
85
8
    tox_events_callback_group_topic(dispatch, on_group_topic);
86
8
    tox_events_callback_group_topic_lock(dispatch, on_group_topic_lock);
87
8
    tox_events_callback_group_moderation(dispatch, on_group_moderation);
88
89
42
    for (uint32_t i = 0; i < NUM_PEERS; ++i) {
90
34
        state->peer_ids[i] = UINT32_MAX;
91
34
    }
92
8
    state->topic_lock = TOX_GROUP_TOPIC_LOCK_ENABLED;
93
94
8
    tox_node_wait_for_self_connected(self);
95
8
}
96
97
static bool topic_is(ToxNode *self, const char *topic)
98
21
{
99
21
    TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
100
21
    size_t len = strlen(topic);
101
21
    if (state->last_topic_len == len && memcmp(state->last_topic, topic, len) == 0) {
102
10
        return true;
103
10
    }
104
105
11
    Tox_Err_Group_State_Query err;
106
11
    size_t current_len = tox_group_get_topic_size(tox_node_get_tox(self), state->group_number, &err);
107
11
    if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_len == len) {
108
7
        uint8_t current_topic[TOX_GROUP_MAX_TOPIC_LENGTH];
109
7
        tox_group_get_topic(tox_node_get_tox(self), state->group_number, current_topic, &err);
110
7
        if (err == TOX_ERR_GROUP_STATE_QUERY_OK && memcmp(current_topic, topic, len) == 0) {
111
5
            state->last_topic_len = current_len;
112
5
            memcpy(state->last_topic, current_topic, current_len);
113
5
            return true;
114
5
        }
115
7
    }
116
6
    return false;
117
11
}
118
119
static bool topic_lock_is(ToxNode *self, Tox_Group_Topic_Lock lock)
120
13
{
121
13
    TopicState *state = (TopicState *)tox_node_get_script_ctx(self);
122
13
    if (state->topic_lock == lock) {
123
4
        return true;
124
4
    }
125
126
9
    Tox_Err_Group_State_Query err;
127
9
    Tox_Group_Topic_Lock current_lock = tox_group_get_topic_lock(tox_node_get_tox(self), state->group_number, &err);
128
9
    if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_lock == lock) {
129
2
        state->topic_lock = current_lock;
130
2
        return true;
131
2
    }
132
7
    return false;
133
9
}
134
135
static void founder_script(ToxNode *self, void *ctx)
136
1
{
137
1
    TopicState *state = (TopicState *)ctx;
138
1
    Tox *tox = tox_node_get_tox(self);
139
1
    common_init(self, state);
140
141
1
    Tox_Err_Group_New err_new;
142
1
    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);
143
1
    ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
144
145
1
    state->peer_ids[0] = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
146
147
1
    tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
148
1
    state->chat_id_ready = true;
149
150
1
    tox_scenario_barrier_wait(self); // 1: Peers joined
151
3
    for (uint32_t i = 1; i < NUM_PEERS; ++i) {
152
2
        WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX);
153
2
    }
154
1
    tox_scenario_barrier_wait(self); // 2: Sync IDs
155
156
    // 3: Initial Topic
157
1
    tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC1, strlen(TOPIC1), nullptr);
158
1
    WAIT_UNTIL(topic_is(self, TOPIC1));
159
1
    tox_scenario_barrier_wait(self);
160
161
    // 4: Disable topic lock
162
1
    tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr);
163
1
    WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED));
164
1
    tox_scenario_barrier_wait(self);
165
166
    // 5: Peers changing topic
167
1
    tox_scenario_barrier_wait(self); // Peer 1 turn
168
1
    WAIT_UNTIL(topic_is(self, "Topic from Peer1"));
169
1
    tox_scenario_barrier_wait(self); // Peer 2 turn
170
1
    WAIT_UNTIL(topic_is(self, "Topic from Peer2"));
171
172
    // 6: Set Peer 2 to Observer
173
1
    tox_group_set_role(tox, state->group_number, state->peer_ids[2], TOX_GROUP_ROLE_OBSERVER, nullptr);
174
1
    tox_scenario_barrier_wait(self);
175
176
    // 7: Peer 1 changes topic, Peer 2 should fail
177
1
    tox_scenario_barrier_wait(self); // Peer 1 turn
178
1
    WAIT_UNTIL(topic_is(self, "Topic again from Peer1"));
179
1
    tox_scenario_barrier_wait(self); // Peer 2 turn (fails)
180
181
    // 8: Enable topic lock
182
1
    tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_ENABLED, nullptr);
183
1
    WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_ENABLED));
184
1
    tox_scenario_barrier_wait(self);
185
186
    // 9: Peer 1 attempts to change topic (fails)
187
1
    tox_scenario_barrier_wait(self);
188
189
    // 10: Founder changes topic
190
1
    tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC2, strlen(TOPIC2), nullptr);
191
1
    WAIT_UNTIL(topic_is(self, TOPIC2));
192
1
    tox_scenario_barrier_wait(self);
193
1
}
194
195
static void peer_script(ToxNode *self, void *ctx)
196
2
{
197
2
    TopicState *state = (TopicState *)ctx;
198
2
    Tox *tox = tox_node_get_tox(self);
199
2
    common_init(self, state);
200
201
2
    ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
202
2
    const TopicState *founder_view = (const TopicState *)tox_node_get_peer_ctx(founder);
203
204
2
    WAIT_UNTIL(founder_view->chat_id_ready);
205
206
2
    char name[16];
207
2
    snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self));
208
2
    tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, nullptr);
209
210
2
    WAIT_UNTIL(state->connected);
211
2
    state->peer_ids[tox_node_get_index(self)] = tox_group_self_get_peer_id(tox, state->group_number, nullptr);
212
213
2
    tox_scenario_barrier_wait(self); // 1: Joined
214
8
    for (uint32_t i = 0; i < NUM_PEERS; ++i) if (i != tox_node_get_index(self)) {
215
4
            WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX);
216
4
        }
217
2
    tox_scenario_barrier_wait(self); // 2: Sync IDs
218
219
    // 3: Topic check
220
2
    WAIT_UNTIL(topic_is(self, TOPIC1));
221
2
    tox_scenario_barrier_wait(self);
222
223
    // 4: Disable topic lock
224
2
    WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED));
225
2
    tox_scenario_barrier_wait(self);
226
227
    // 5: Peers changing topic
228
2
    if (tox_node_get_index(self) == 1) {
229
1
        tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic from Peer1", strlen("Topic from Peer1"), nullptr);
230
1
    }
231
2
    tox_scenario_barrier_wait(self); // Peer 1 turn
232
2
    WAIT_UNTIL(topic_is(self, "Topic from Peer1"));
233
234
2
    if (tox_node_get_index(self) == 2) {
235
1
        tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic from Peer2", strlen("Topic from Peer2"), nullptr);
236
1
    }
237
2
    tox_scenario_barrier_wait(self); // Peer 2 turn
238
2
    WAIT_UNTIL(topic_is(self, "Topic from Peer2"));
239
240
    // 6: Set Peer 2 to Observer
241
2
    tox_scenario_barrier_wait(self);
242
2
    if (tox_node_get_index(self) == 2) {
243
1
        WAIT_UNTIL(state->self_role == TOX_GROUP_ROLE_OBSERVER);
244
1
    }
245
246
    // 7: Peer 1 changes topic, Peer 2 should fail
247
2
    if (tox_node_get_index(self) == 1) {
248
1
        tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic again from Peer1", strlen("Topic again from Peer1"), nullptr);
249
1
    }
250
2
    tox_scenario_barrier_wait(self); // Peer 1 turn
251
2
    WAIT_UNTIL(topic_is(self, "Topic again from Peer1"));
252
253
2
    if (tox_node_get_index(self) == 2) {
254
1
        Tox_Err_Group_Topic_Set err;
255
1
        tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Fail topic", 10, &err);
256
1
        ck_assert(err == TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS);
257
1
    }
258
2
    tox_scenario_barrier_wait(self); // Peer 2 turn (fails)
259
260
    // 8: Enable topic lock
261
2
    WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_ENABLED));
262
2
    tox_scenario_barrier_wait(self);
263
264
    // 9: Peer 1 attempts to change topic (fails)
265
2
    if (tox_node_get_index(self) == 1) {
266
1
        Tox_Err_Group_Topic_Set err;
267
1
        tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Fail topic 2", 12, &err);
268
1
        ck_assert(err == TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS);
269
1
    }
270
2
    tox_scenario_barrier_wait(self);
271
272
    // 10: Founder changes topic
273
2
    tox_scenario_barrier_wait(self);
274
2
    WAIT_UNTIL(topic_is(self, TOPIC2));
275
2
}
276
277
int main(int argc, char *argv[])
278
3
{
279
3
    ToxScenario *s = tox_scenario_new(argc, argv, 300000);
280
3
    TopicState states[NUM_PEERS] = {0};
281
3
    ToxNode *nodes[NUM_PEERS];
282
283
3
    nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(TopicState));
284
13
    for (uint32_t i = 1; i < NUM_PEERS; ++i) {
285
10
        char alias[16];
286
10
        snprintf(alias, sizeof(alias), "Peer%u", i);
287
10
        nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(TopicState));
288
10
    }
289
290
16
    for (uint32_t i = 0; i < NUM_PEERS; ++i) {
291
72
        for (uint32_t j = 0; j < NUM_PEERS; ++j) {
292
59
            if (i != j) {
293
46
                tox_node_bootstrap(nodes[i], nodes[j]);
294
46
                tox_node_friend_add(nodes[i], nodes[j]);
295
46
            }
296
59
        }
297
13
    }
298
299
3
    ToxScenarioStatus res = tox_scenario_run(s);
300
3
    if (res != TOX_SCENARIO_DONE) {
301
0
        return 1;
302
0
    }
303
304
3
    tox_scenario_free(s);
305
3
    return 0;
306
3
}
307
308
#undef TOPIC2
309
#undef TOPIC1
310
#undef GROUP_NAME_LEN
311
#undef GROUP_NAME
312
#undef NUM_PEERS