/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 |