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