summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--auto_tests/file_transfer_test.c429
-rw-r--r--auto_tests/helpers.h10
-rw-r--r--auto_tests/tox_test.c311
-rw-r--r--toxcore/Messenger.c158
-rw-r--r--toxcore/Messenger.h2
-rw-r--r--toxcore/util.c10
-rw-r--r--toxcore/util.h3
8 files changed, 544 insertions, 380 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8bba3519..9acb97d2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -524,6 +524,7 @@ auto_test(conference)
524auto_test(crypto MSVC_DONT_BUILD) 524auto_test(crypto MSVC_DONT_BUILD)
525auto_test(dht MSVC_DONT_BUILD) 525auto_test(dht MSVC_DONT_BUILD)
526auto_test(encryptsave) 526auto_test(encryptsave)
527auto_test(file_transfer)
527auto_test(lan_discovery) 528auto_test(lan_discovery)
528auto_test(messenger MSVC_DONT_BUILD) 529auto_test(messenger MSVC_DONT_BUILD)
529auto_test(network) 530auto_test(network)
diff --git a/auto_tests/file_transfer_test.c b/auto_tests/file_transfer_test.c
new file mode 100644
index 00000000..7247a188
--- /dev/null
+++ b/auto_tests/file_transfer_test.c
@@ -0,0 +1,429 @@
1/* File transfer test.
2 */
3
4#ifndef _XOPEN_SOURCE
5#define _XOPEN_SOURCE 600
6#endif
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "check_compat.h"
13
14#include <stdio.h>
15#include <stdlib.h>
16#include <time.h>
17
18#include "../toxcore/ccompat.h"
19#include "../toxcore/tox.h"
20#include "../toxcore/util.h"
21
22#include "helpers.h"
23
24/* The Travis-CI container responds poorly to ::1 as a localhost address
25 * You're encouraged to -D FORCE_TESTS_IPV6 on a local test */
26#ifdef FORCE_TESTS_IPV6
27#define TOX_LOCALHOST "::1"
28#else
29#define TOX_LOCALHOST "127.0.0.1"
30#endif
31
32static void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
33{
34 if (length == 7 && memcmp("Gentoo", data, 7) == 0) {
35 tox_friend_add_norequest(m, public_key, nullptr);
36 }
37}
38
39static uint64_t size_recv;
40static uint64_t sending_pos;
41
42static uint8_t file_cmp_id[TOX_FILE_ID_LENGTH];
43static uint32_t file_accepted;
44static uint64_t file_size;
45static void tox_file_receive(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t filesize,
46 const uint8_t *filename, size_t filename_length, void *userdata)
47{
48 if (kind != TOX_FILE_KIND_DATA) {
49 ck_abort_msg("Bad kind");
50 }
51
52 if (!(filename_length == sizeof("Gentoo.exe") && memcmp(filename, "Gentoo.exe", sizeof("Gentoo.exe")) == 0)) {
53 ck_abort_msg("Bad filename");
54 }
55
56 uint8_t file_id[TOX_FILE_ID_LENGTH];
57
58 if (!tox_file_get_file_id(tox, friend_number, file_number, file_id, nullptr)) {
59 ck_abort_msg("tox_file_get_file_id error");
60 }
61
62 if (memcmp(file_id, file_cmp_id, TOX_FILE_ID_LENGTH) != 0) {
63 ck_abort_msg("bad file_id");
64 }
65
66 uint8_t empty[TOX_FILE_ID_LENGTH] = {0};
67
68 if (memcmp(empty, file_cmp_id, TOX_FILE_ID_LENGTH) == 0) {
69 ck_abort_msg("empty file_id");
70 }
71
72 file_size = filesize;
73
74 if (filesize) {
75 sending_pos = size_recv = 1337;
76
77 TOX_ERR_FILE_SEEK err_s;
78
79 if (!tox_file_seek(tox, friend_number, file_number, 1337, &err_s)) {
80 ck_abort_msg("tox_file_seek error");
81 }
82
83 ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_OK, "tox_file_seek wrong error");
84 } else {
85 sending_pos = size_recv = 0;
86 }
87
88 TOX_ERR_FILE_CONTROL error;
89
90 if (tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, &error)) {
91 ++file_accepted;
92 } else {
93 ck_abort_msg("tox_file_control failed. %i", error);
94 }
95
96 TOX_ERR_FILE_SEEK err_s;
97
98 if (tox_file_seek(tox, friend_number, file_number, 1234, &err_s)) {
99 ck_abort_msg("tox_file_seek no error");
100 }
101
102 ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_DENIED, "tox_file_seek wrong error");
103}
104
105static uint32_t sendf_ok;
106static void file_print_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control,
107 void *userdata)
108{
109 /* First send file num is 0.*/
110 if (file_number == 0 && control == TOX_FILE_CONTROL_RESUME) {
111 sendf_ok = 1;
112 }
113}
114
115static uint64_t max_sending;
116static bool m_send_reached;
117static uint8_t sending_num;
118static bool file_sending_done;
119static void tox_file_chunk_request(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position,
120 size_t length, void *user_data)
121{
122 if (!sendf_ok) {
123 ck_abort_msg("Didn't get resume control");
124 }
125
126 if (sending_pos != position) {
127 ck_abort_msg("Bad position %llu", (unsigned long long)position);
128 }
129
130 if (length == 0) {
131 if (file_sending_done) {
132 ck_abort_msg("File sending already done.");
133 }
134
135 file_sending_done = 1;
136 return;
137 }
138
139 if (position + length > max_sending) {
140 if (m_send_reached) {
141 ck_abort_msg("Requested done file transfer.");
142 }
143
144 length = max_sending - position;
145 m_send_reached = 1;
146 }
147
148 TOX_ERR_FILE_SEND_CHUNK error;
149 VLA(uint8_t, f_data, length);
150 memset(f_data, sending_num, length);
151
152 if (tox_file_send_chunk(tox, friend_number, file_number, position, f_data, length, &error)) {
153 ++sending_num;
154 sending_pos += length;
155 } else {
156 ck_abort_msg("Could not send chunk, error num=%d pos=%d len=%d", (int)error, (int)position, (int)length);
157 }
158
159 if (error != TOX_ERR_FILE_SEND_CHUNK_OK) {
160 ck_abort_msg("Wrong error code");
161 }
162}
163
164
165static uint8_t num;
166static bool file_recv;
167static void write_file(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data,
168 size_t length, void *user_data)
169{
170 if (size_recv != position) {
171 ck_abort_msg("Bad position");
172 }
173
174 if (length == 0) {
175 file_recv = 1;
176 return;
177 }
178
179 VLA(uint8_t, f_data, length);
180 memset(f_data, num, length);
181 ++num;
182
183 if (memcmp(f_data, data, length) == 0) {
184 size_recv += length;
185 } else {
186 ck_abort_msg("FILE_CORRUPTED");
187 }
188}
189
190START_TEST(test_few_clients)
191{
192 printf("Starting test: few_clients\n");
193 uint32_t index[] = { 1, 2, 3 };
194 long long unsigned int cur_time = time(nullptr);
195 TOX_ERR_NEW t_n_error;
196 Tox *tox1 = tox_new_log(nullptr, &t_n_error, &index[0]);
197 ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error");
198 Tox *tox2 = tox_new_log(nullptr, &t_n_error, &index[1]);
199 ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error");
200 Tox *tox3 = tox_new_log(nullptr, &t_n_error, &index[2]);
201 ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error");
202
203 ck_assert_msg(tox1 && tox2 && tox3, "Failed to create 3 tox instances");
204
205 {
206 TOX_ERR_GET_PORT error;
207 uint16_t first_port = tox_self_get_udp_port(tox1, &error);
208 ck_assert_msg(33445 <= first_port && first_port <= 33545 - 2,
209 "First Tox instance did not bind to udp port inside [33445, 33543].\n");
210 ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
211
212 ck_assert_msg(tox_self_get_udp_port(tox2, &error) == first_port + 1,
213 "Second Tox instance did not bind to udp port %d.\n", first_port + 1);
214 ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
215
216 ck_assert_msg(tox_self_get_udp_port(tox3, &error) == first_port + 2,
217 "Third Tox instance did not bind to udp port %d.\n", first_port + 2);
218 ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
219 }
220
221 tox_callback_friend_request(tox2, accept_friend_request);
222 uint8_t address[TOX_ADDRESS_SIZE];
223 tox_self_get_address(tox2, address);
224 uint32_t test = tox_friend_add(tox3, address, (const uint8_t *)"Gentoo", 7, nullptr);
225 ck_assert_msg(test == 0, "Failed to add friend error code: %i", test);
226
227 uint8_t dhtKey[TOX_PUBLIC_KEY_SIZE];
228 tox_self_get_dht_id(tox1, dhtKey);
229 uint16_t dhtPort = tox_self_get_udp_port(tox1, nullptr);
230
231 tox_bootstrap(tox2, TOX_LOCALHOST, dhtPort, dhtKey, nullptr);
232 tox_bootstrap(tox3, TOX_LOCALHOST, dhtPort, dhtKey, nullptr);
233
234 printf("Waiting for toxes to come online\n");
235
236 while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE ||
237 tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE ||
238 tox_self_get_connection_status(tox3) == TOX_CONNECTION_NONE ||
239 tox_friend_get_connection_status(tox2, 0, nullptr) == TOX_CONNECTION_NONE ||
240 tox_friend_get_connection_status(tox3, 0, nullptr) == TOX_CONNECTION_NONE) {
241 tox_iterate(tox1, nullptr);
242 tox_iterate(tox2, nullptr);
243 tox_iterate(tox3, nullptr);
244
245 printf("Connections: self (%d, %d, %d), friends (%d, %d)\n",
246 tox_self_get_connection_status(tox1),
247 tox_self_get_connection_status(tox2),
248 tox_self_get_connection_status(tox3),
249 tox_friend_get_connection_status(tox2, 0, nullptr),
250 tox_friend_get_connection_status(tox3, 0, nullptr));
251 c_sleep(1000);
252 }
253
254 printf("Starting file transfer test.\n");
255
256 file_accepted = file_size = sendf_ok = size_recv = 0;
257 file_recv = 0;
258 max_sending = UINT64_MAX;
259 long long unsigned int f_time = time(nullptr);
260 tox_callback_file_recv_chunk(tox3, write_file);
261 tox_callback_file_recv_control(tox2, file_print_control);
262 tox_callback_file_chunk_request(tox2, tox_file_chunk_request);
263 tox_callback_file_recv_control(tox3, file_print_control);
264 tox_callback_file_recv(tox3, tox_file_receive);
265 uint64_t totalf_size = 100 * 1024 * 1024;
266 uint32_t fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr, (const uint8_t *)"Gentoo.exe",
267 sizeof("Gentoo.exe"), nullptr);
268 ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
269
270 TOX_ERR_FILE_GET gfierr;
271 ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
272 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
273 ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
274 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
275 ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
276 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
277
278 while (1) {
279 tox_iterate(tox1, nullptr);
280 tox_iterate(tox2, nullptr);
281 tox_iterate(tox3, nullptr);
282
283 if (file_sending_done) {
284 if (sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size && sending_pos == size_recv
285 && file_accepted == 1) {
286 break;
287 }
288
289 ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %llu %llu %llu", sendf_ok, file_recv,
290 totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1,
291 (unsigned long long)totalf_size, (unsigned long long)size_recv,
292 (unsigned long long)sending_pos);
293 }
294
295 uint32_t tox1_interval = tox_iteration_interval(tox1);
296 uint32_t tox2_interval = tox_iteration_interval(tox2);
297 uint32_t tox3_interval = tox_iteration_interval(tox3);
298
299 c_sleep(MIN(tox1_interval, MIN(tox2_interval, tox3_interval)));
300 }
301
302 printf("100MB file sent in %llu seconds\n", time(nullptr) - f_time);
303
304 printf("Starting file streaming transfer test.\n");
305
306 file_sending_done = file_accepted = file_size = sendf_ok = size_recv = 0;
307 file_recv = 0;
308 tox_callback_file_recv_chunk(tox3, write_file);
309 tox_callback_file_recv_control(tox2, file_print_control);
310 tox_callback_file_chunk_request(tox2, tox_file_chunk_request);
311 tox_callback_file_recv_control(tox3, file_print_control);
312 tox_callback_file_recv(tox3, tox_file_receive);
313 totalf_size = UINT64_MAX;
314 fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr,
315 (const uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), nullptr);
316 ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
317
318 ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
319 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
320 ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
321 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
322 ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
323 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
324
325 max_sending = 100 * 1024;
326 m_send_reached = 0;
327
328 while (1) {
329 tox_iterate(tox1, nullptr);
330 tox_iterate(tox2, nullptr);
331 tox_iterate(tox3, nullptr);
332
333 if (file_sending_done) {
334 if (sendf_ok && file_recv && m_send_reached && totalf_size == file_size && size_recv == max_sending
335 && sending_pos == size_recv && file_accepted == 1) {
336 break;
337 }
338
339 ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %u %llu %llu %llu %llu", sendf_ok, file_recv,
340 m_send_reached, totalf_size == file_size, size_recv == max_sending, sending_pos == size_recv, file_accepted == 1,
341 (unsigned long long)totalf_size, (unsigned long long)file_size,
342 (unsigned long long)size_recv, (unsigned long long)sending_pos);
343 }
344
345 uint32_t tox1_interval = tox_iteration_interval(tox1);
346 uint32_t tox2_interval = tox_iteration_interval(tox2);
347 uint32_t tox3_interval = tox_iteration_interval(tox3);
348
349 c_sleep(MIN(tox1_interval, MIN(tox2_interval, tox3_interval)));
350 }
351
352 printf("Starting file 0 transfer test.\n");
353
354 file_sending_done = file_accepted = file_size = sendf_ok = size_recv = 0;
355 file_recv = 0;
356 tox_callback_file_recv_chunk(tox3, write_file);
357 tox_callback_file_recv_control(tox2, file_print_control);
358 tox_callback_file_chunk_request(tox2, tox_file_chunk_request);
359 tox_callback_file_recv_control(tox3, file_print_control);
360 tox_callback_file_recv(tox3, tox_file_receive);
361 totalf_size = 0;
362 fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr,
363 (const uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), nullptr);
364 ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
365
366 ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
367 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
368 ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
369 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
370 ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
371 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
372
373 while (1) {
374 tox_iterate(tox1, nullptr);
375 tox_iterate(tox2, nullptr);
376 tox_iterate(tox3, nullptr);
377
378 if (file_sending_done) {
379 if (sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size && sending_pos == size_recv
380 && file_accepted == 1) {
381 break;
382 }
383
384 ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %llu %llu %llu", sendf_ok, file_recv,
385 totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1,
386 (unsigned long long)totalf_size, (unsigned long long)size_recv,
387 (unsigned long long)sending_pos);
388 }
389
390 uint32_t tox1_interval = tox_iteration_interval(tox1);
391 uint32_t tox2_interval = tox_iteration_interval(tox2);
392 uint32_t tox3_interval = tox_iteration_interval(tox3);
393
394 c_sleep(MIN(tox1_interval, MIN(tox2_interval, tox3_interval)));
395 }
396
397 printf("test_few_clients succeeded, took %llu seconds\n", time(nullptr) - cur_time);
398
399 tox_kill(tox1);
400 tox_kill(tox2);
401 tox_kill(tox3);
402}
403END_TEST
404
405static Suite *tox_suite(void)
406{
407 Suite *s = suite_create("Tox few clients");
408
409 DEFTESTCASE(few_clients);
410
411 return s;
412}
413
414int main(int argc, char *argv[])
415{
416 setvbuf(stdout, nullptr, _IONBF, 0);
417 srand((unsigned int) time(nullptr));
418
419 Suite *tox = tox_suite();
420 SRunner *test_runner = srunner_create(tox);
421
422 int number_failed = 0;
423 srunner_run_all(test_runner, CK_NORMAL);
424 number_failed = srunner_ntests_failed(test_runner);
425
426 srunner_free(test_runner);
427
428 return number_failed;
429}
diff --git a/auto_tests/helpers.h b/auto_tests/helpers.h
index ac58684d..0c88f4a1 100644
--- a/auto_tests/helpers.h
+++ b/auto_tests/helpers.h
@@ -42,18 +42,10 @@ static const char *tox_log_level_name(TOX_LOG_LEVEL level)
42static void print_debug_log(Tox *m, TOX_LOG_LEVEL level, const char *path, uint32_t line, const char *func, 42static void print_debug_log(Tox *m, TOX_LOG_LEVEL level, const char *path, uint32_t line, const char *func,
43 const char *message, void *user_data) 43 const char *message, void *user_data)
44{ 44{
45 if (level == TOX_LOG_LEVEL_TRACE) {
46 return;
47 }
48
49 if (strncmp(message, "Bound successfully to ", strlen("Bound successfully to "))) {
50 return;
51 }
52
53 uint32_t index = user_data ? *(uint32_t *)user_data : 0; 45 uint32_t index = user_data ? *(uint32_t *)user_data : 0;
54 const char *file = strrchr(path, '/'); 46 const char *file = strrchr(path, '/');
55 file = file ? file + 1 : path; 47 file = file ? file + 1 : path;
56 printf("[#%d] %s %s:%d\t%s:\t%s\n", index, tox_log_level_name(level), file, line, func, message); 48 fprintf(stderr, "[#%d] %s %s:%d\t%s:\t%s\n", index, tox_log_level_name(level), file, line, func, message);
57} 49}
58 50
59Tox *tox_new_log(struct Tox_Options *options, TOX_ERR_NEW *err, void *log_user_data) 51Tox *tox_new_log(struct Tox_Options *options, TOX_ERR_NEW *err, void *log_user_data)
diff --git a/auto_tests/tox_test.c b/auto_tests/tox_test.c
index 82c18b37..d6613a9a 100644
--- a/auto_tests/tox_test.c
+++ b/auto_tests/tox_test.c
@@ -133,174 +133,6 @@ static void handle_custom_packet(Tox *m, uint32_t friend_num, const uint8_t *dat
133 return; 133 return;
134} 134}
135 135
136static uint64_t size_recv;
137static uint64_t sending_pos;
138
139static uint8_t file_cmp_id[TOX_FILE_ID_LENGTH];
140static uint32_t file_accepted;
141static uint64_t file_size;
142static void tox_file_receive(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t filesize,
143 const uint8_t *filename, size_t filename_length, void *userdata)
144{
145 if (*((uint32_t *)userdata) != 974536) {
146 return;
147 }
148
149 if (kind != TOX_FILE_KIND_DATA) {
150 ck_abort_msg("Bad kind");
151 }
152
153 if (!(filename_length == sizeof("Gentoo.exe") && memcmp(filename, "Gentoo.exe", sizeof("Gentoo.exe")) == 0)) {
154 ck_abort_msg("Bad filename");
155 }
156
157 uint8_t file_id[TOX_FILE_ID_LENGTH];
158
159 if (!tox_file_get_file_id(tox, friend_number, file_number, file_id, nullptr)) {
160 ck_abort_msg("tox_file_get_file_id error");
161 }
162
163 if (memcmp(file_id, file_cmp_id, TOX_FILE_ID_LENGTH) != 0) {
164 ck_abort_msg("bad file_id");
165 }
166
167 uint8_t empty[TOX_FILE_ID_LENGTH] = {0};
168
169 if (memcmp(empty, file_cmp_id, TOX_FILE_ID_LENGTH) == 0) {
170 ck_abort_msg("empty file_id");
171 }
172
173 file_size = filesize;
174
175 if (filesize) {
176 sending_pos = size_recv = 1337;
177
178 TOX_ERR_FILE_SEEK err_s;
179
180 if (!tox_file_seek(tox, friend_number, file_number, 1337, &err_s)) {
181 ck_abort_msg("tox_file_seek error");
182 }
183
184 ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_OK, "tox_file_seek wrong error");
185 } else {
186 sending_pos = size_recv = 0;
187 }
188
189 TOX_ERR_FILE_CONTROL error;
190
191 if (tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, &error)) {
192 ++file_accepted;
193 } else {
194 ck_abort_msg("tox_file_control failed. %i", error);
195 }
196
197 TOX_ERR_FILE_SEEK err_s;
198
199 if (tox_file_seek(tox, friend_number, file_number, 1234, &err_s)) {
200 ck_abort_msg("tox_file_seek no error");
201 }
202
203 ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_DENIED, "tox_file_seek wrong error");
204}
205
206static uint32_t sendf_ok;
207static void file_print_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control,
208 void *userdata)
209{
210 if (*((uint32_t *)userdata) != 974536) {
211 return;
212 }
213
214 /* First send file num is 0.*/
215 if (file_number == 0 && control == TOX_FILE_CONTROL_RESUME) {
216 sendf_ok = 1;
217 }
218}
219
220static uint64_t max_sending;
221static bool m_send_reached;
222static uint8_t sending_num;
223static bool file_sending_done;
224static void tox_file_chunk_request(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position,
225 size_t length,
226 void *user_data)
227{
228 if (*((uint32_t *)user_data) != 974536) {
229 return;
230 }
231
232 if (!sendf_ok) {
233 ck_abort_msg("Didn't get resume control");
234 }
235
236 if (sending_pos != position) {
237 ck_abort_msg("Bad position %llu", (unsigned long long)position);
238 }
239
240 if (length == 0) {
241 if (file_sending_done) {
242 ck_abort_msg("File sending already done.");
243 }
244
245 file_sending_done = 1;
246 return;
247 }
248
249 if (position + length > max_sending) {
250 if (m_send_reached) {
251 ck_abort_msg("Requested done file transfer.");
252 }
253
254 length = max_sending - position;
255 m_send_reached = 1;
256 }
257
258 TOX_ERR_FILE_SEND_CHUNK error;
259 VLA(uint8_t, f_data, length);
260 memset(f_data, sending_num, length);
261
262 if (tox_file_send_chunk(tox, friend_number, file_number, position, f_data, length, &error)) {
263 ++sending_num;
264 sending_pos += length;
265 } else {
266 ck_abort_msg("Could not send chunk %i", error);
267 }
268
269 if (error != TOX_ERR_FILE_SEND_CHUNK_OK) {
270 ck_abort_msg("Wrong error code");
271 }
272}
273
274
275static uint8_t num;
276static bool file_recv;
277static void write_file(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data,
278 size_t length, void *user_data)
279{
280 if (*((uint32_t *)user_data) != 974536) {
281 return;
282 }
283
284 if (size_recv != position) {
285 ck_abort_msg("Bad position");
286 }
287
288 if (length == 0) {
289 file_recv = 1;
290 return;
291 }
292
293 VLA(uint8_t, f_data, length);
294 memset(f_data, num, length);
295 ++num;
296
297 if (memcmp(f_data, data, length) == 0) {
298 size_recv += length;
299 } else {
300 ck_abort_msg("FILE_CORRUPTED");
301 }
302}
303
304static unsigned int connected_t1; 136static unsigned int connected_t1;
305static void tox_connection_status(Tox *tox, TOX_CONNECTION connection_status, void *user_data) 137static void tox_connection_status(Tox *tox, TOX_CONNECTION connection_status, void *user_data)
306{ 138{
@@ -578,149 +410,6 @@ START_TEST(test_few_clients)
578 c_sleep(50); 410 c_sleep(50);
579 } 411 }
580 412
581 printf("Starting file transfer test.\n");
582
583 file_accepted = file_size = sendf_ok = size_recv = 0;
584 file_recv = 0;
585 max_sending = UINT64_MAX;
586 long long unsigned int f_time = time(nullptr);
587 tox_callback_file_recv_chunk(tox3, write_file);
588 tox_callback_file_recv_control(tox2, file_print_control);
589 tox_callback_file_chunk_request(tox2, tox_file_chunk_request);
590 tox_callback_file_recv_control(tox3, file_print_control);
591 tox_callback_file_recv(tox3, tox_file_receive);
592 uint64_t totalf_size = 100 * 1024 * 1024;
593 uint32_t fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr, (const uint8_t *)"Gentoo.exe",
594 sizeof("Gentoo.exe"), nullptr);
595 ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
596
597 TOX_ERR_FILE_GET gfierr;
598 ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
599 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
600 ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
601 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
602 ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
603 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
604
605 while (1) {
606 tox_iterate(tox1, &to_compare);
607 tox_iterate(tox2, &to_compare);
608 tox_iterate(tox3, &to_compare);
609
610 if (file_sending_done) {
611 if (sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size && sending_pos == size_recv
612 && file_accepted == 1) {
613 break;
614 }
615
616 ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %llu %llu %llu", sendf_ok, file_recv,
617 totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1,
618 (unsigned long long)totalf_size, (unsigned long long)size_recv,
619 (unsigned long long)sending_pos);
620 }
621
622 uint32_t tox1_interval = tox_iteration_interval(tox1);
623 uint32_t tox2_interval = tox_iteration_interval(tox2);
624 uint32_t tox3_interval = tox_iteration_interval(tox3);
625
626 c_sleep(MIN(tox1_interval, MIN(tox2_interval, tox3_interval)));
627 }
628
629 printf("100MB file sent in %llu seconds\n", time(nullptr) - f_time);
630
631 printf("Starting file streaming transfer test.\n");
632
633 file_sending_done = file_accepted = file_size = sendf_ok = size_recv = 0;
634 file_recv = 0;
635 tox_callback_file_recv_chunk(tox3, write_file);
636 tox_callback_file_recv_control(tox2, file_print_control);
637 tox_callback_file_chunk_request(tox2, tox_file_chunk_request);
638 tox_callback_file_recv_control(tox3, file_print_control);
639 tox_callback_file_recv(tox3, tox_file_receive);
640 totalf_size = UINT64_MAX;
641 fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr,
642 (const uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), nullptr);
643 ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
644
645 ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
646 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
647 ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
648 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
649 ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
650 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
651
652 max_sending = 100 * 1024;
653 m_send_reached = 0;
654
655 while (1) {
656 tox_iterate(tox1, &to_compare);
657 tox_iterate(tox2, &to_compare);
658 tox_iterate(tox3, &to_compare);
659
660 if (file_sending_done) {
661 if (sendf_ok && file_recv && m_send_reached && totalf_size == file_size && size_recv == max_sending
662 && sending_pos == size_recv && file_accepted == 1) {
663 break;
664 }
665
666 ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %u %llu %llu %llu %llu", sendf_ok, file_recv,
667 m_send_reached, totalf_size == file_size, size_recv == max_sending, sending_pos == size_recv, file_accepted == 1,
668 (unsigned long long)totalf_size, (unsigned long long)file_size,
669 (unsigned long long)size_recv, (unsigned long long)sending_pos);
670 }
671
672 uint32_t tox1_interval = tox_iteration_interval(tox1);
673 uint32_t tox2_interval = tox_iteration_interval(tox2);
674 uint32_t tox3_interval = tox_iteration_interval(tox3);
675
676 c_sleep(MIN(tox1_interval, MIN(tox2_interval, tox3_interval)));
677 }
678
679 printf("Starting file 0 transfer test.\n");
680
681 file_sending_done = file_accepted = file_size = sendf_ok = size_recv = 0;
682 file_recv = 0;
683 tox_callback_file_recv_chunk(tox3, write_file);
684 tox_callback_file_recv_control(tox2, file_print_control);
685 tox_callback_file_chunk_request(tox2, tox_file_chunk_request);
686 tox_callback_file_recv_control(tox3, file_print_control);
687 tox_callback_file_recv(tox3, tox_file_receive);
688 totalf_size = 0;
689 fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr,
690 (const uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), nullptr);
691 ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
692
693 ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
694 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
695 ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
696 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
697 ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
698 ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
699
700 while (1) {
701 tox_iterate(tox1, &to_compare);
702 tox_iterate(tox2, &to_compare);
703 tox_iterate(tox3, &to_compare);
704
705 if (file_sending_done) {
706 if (sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size && sending_pos == size_recv
707 && file_accepted == 1) {
708 break;
709 }
710
711 ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %llu %llu %llu", sendf_ok, file_recv,
712 totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1,
713 (unsigned long long)totalf_size, (unsigned long long)size_recv,
714 (unsigned long long)sending_pos);
715 }
716
717 uint32_t tox1_interval = tox_iteration_interval(tox1);
718 uint32_t tox2_interval = tox_iteration_interval(tox2);
719 uint32_t tox3_interval = tox_iteration_interval(tox3);
720
721 c_sleep(MIN(tox1_interval, MIN(tox2_interval, tox3_interval)));
722 }
723
724 printf("test_few_clients succeeded, took %llu seconds\n", time(nullptr) - cur_time); 413 printf("test_few_clients succeeded, took %llu seconds\n", time(nullptr) - cur_time);
725 414
726 tox_options_free(options); 415 tox_options_free(options);
diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c
index 3baf7401..a6bccfdc 100644
--- a/toxcore/Messenger.c
+++ b/toxcore/Messenger.c
@@ -1482,119 +1482,159 @@ uint64_t file_dataremaining(const Messenger *m, int32_t friendnumber, uint8_t fi
1482 return 0; 1482 return 0;
1483 } 1483 }
1484 1484
1485 const struct File_Transfers *const sending = &m->friendlist[friendnumber].file_sending[filenumber];
1486
1485 if (send_receive == 0) { 1487 if (send_receive == 0) {
1486 if (m->friendlist[friendnumber].file_sending[filenumber].status == FILESTATUS_NONE) { 1488 if (sending->status == FILESTATUS_NONE) {
1487 return 0; 1489 return 0;
1488 } 1490 }
1489 1491
1490 return m->friendlist[friendnumber].file_sending[filenumber].size - 1492 return sending->size - sending->transferred;
1491 m->friendlist[friendnumber].file_sending[filenumber].transferred;
1492 } 1493 }
1493 1494
1494 if (m->friendlist[friendnumber].file_receiving[filenumber].status == FILESTATUS_NONE) { 1495 const struct File_Transfers *const receiving = &m->friendlist[friendnumber].file_receiving[filenumber];
1496
1497 if (receiving->status == FILESTATUS_NONE) {
1495 return 0; 1498 return 0;
1496 } 1499 }
1497 1500
1498 return m->friendlist[friendnumber].file_receiving[filenumber].size - 1501 return receiving->size - receiving->transferred;
1499 m->friendlist[friendnumber].file_receiving[filenumber].transferred;
1500} 1502}
1501 1503
1502static void do_reqchunk_filecb(Messenger *m, int32_t friendnumber, void *userdata) 1504/**
1505 * Iterate over all file transfers and request chunks (from the client) for each
1506 * of them.
1507 *
1508 * The free_slots parameter is updated by this function.
1509 *
1510 * @param m Our messenger object.
1511 * @param friendnumber The friend we're sending files to.
1512 * @param userdata The client userdata to pass along to chunk request callbacks.
1513 * @param free_slots A pointer to the number of free send queue slots in the
1514 * crypto connection.
1515 *
1516 * @return true if there are still file transfers ongoing, false if all file
1517 * transfers are complete.
1518 */
1519static bool do_all_filetransfers(Messenger *m, int32_t friendnumber, void *userdata, uint32_t *free_slots)
1503{ 1520{
1504 if (!m->friendlist[friendnumber].num_sending_files) { 1521 Friend *const friendcon = &m->friendlist[friendnumber];
1505 return; 1522 uint32_t num = friendcon->num_sending_files;
1506 }
1507 1523
1508 int free_slots = crypto_num_free_sendqueue_slots(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, 1524 bool any_active_fts = false;
1509 m->friendlist[friendnumber].friendcon_id));
1510
1511 if (free_slots < MIN_SLOTS_FREE) {
1512 free_slots = 0;
1513 } else {
1514 free_slots -= MIN_SLOTS_FREE;
1515 }
1516 1525
1517 unsigned int i, num = m->friendlist[friendnumber].num_sending_files; 1526 // Iterate over all file transfers, including inactive ones. I.e. we always
1518 1527 // iterate exactly MAX_CONCURRENT_FILE_PIPES times.
1519 for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { 1528 for (uint32_t i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) {
1520 struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[i]; 1529 struct File_Transfers *const ft = &friendcon->file_sending[i];
1521 1530
1531 // Any status other than NONE means the file transfer is active.
1522 if (ft->status != FILESTATUS_NONE) { 1532 if (ft->status != FILESTATUS_NONE) {
1533 any_active_fts = true;
1523 --num; 1534 --num;
1524 1535
1525 if (ft->status == FILESTATUS_FINISHED) { 1536 // If the file transfer is complete, we request a chunk of size 0.
1526 /* Check if file was entirely sent. */ 1537 if (ft->status == FILESTATUS_FINISHED && friend_received_packet(m, friendnumber, ft->last_packet_number) == 0) {
1527 if (friend_received_packet(m, friendnumber, ft->last_packet_number) == 0) { 1538 if (m->file_reqchunk) {
1528 if (m->file_reqchunk) { 1539 m->file_reqchunk(m, friendnumber, i, ft->transferred, 0, userdata);
1529 (*m->file_reqchunk)(m, friendnumber, i, ft->transferred, 0, userdata);
1530 }
1531
1532 ft->status = FILESTATUS_NONE;
1533 --m->friendlist[friendnumber].num_sending_files;
1534 } 1540 }
1535 }
1536 1541
1537 /* TODO(irungentoo): if file is too slow, switch to the next. */ 1542 // Now it's inactive, we're no longer sending this.
1538 if (ft->slots_allocated > (unsigned int)free_slots) { 1543 ft->status = FILESTATUS_NONE;
1539 free_slots = 0; 1544 --friendcon->num_sending_files;
1540 } else {
1541 free_slots -= ft->slots_allocated;
1542 } 1545 }
1546
1547 // Decrease free slots by the number of slots this FT uses.
1548 *free_slots = max_s32(0, (int32_t) * free_slots - ft->slots_allocated);
1543 } 1549 }
1544 1550
1545 while (ft->status == FILESTATUS_TRANSFERRING && (ft->paused == FILE_PAUSE_NOT)) { 1551 if (ft->status == FILESTATUS_TRANSFERRING && ft->paused == FILE_PAUSE_NOT) {
1546 if (max_speed_reached(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, 1552 if (max_speed_reached(m->net_crypto, friend_connection_crypt_connection_id(
1547 m->friendlist[friendnumber].friendcon_id))) { 1553 m->fr_c, friendcon->friendcon_id))) {
1548 free_slots = 0; 1554 *free_slots = 0;
1549 } 1555 }
1550 1556
1551 if (free_slots == 0) { 1557 if (*free_slots == 0) {
1552 break; 1558 continue;
1553 } 1559 }
1554 1560
1555 uint16_t length = MAX_FILE_DATA_SIZE;
1556
1557 if (ft->size == 0) { 1561 if (ft->size == 0) {
1558 /* Send 0 data to friend if file is 0 length. */ 1562 /* Send 0 data to friend if file is 0 length. */
1559 file_data(m, friendnumber, i, 0, nullptr, 0); 1563 file_data(m, friendnumber, i, 0, nullptr, 0);
1560 break; 1564 continue;
1561 } 1565 }
1562 1566
1563 if (ft->size == ft->requested) { 1567 if (ft->size == ft->requested) {
1564 break; 1568 // This file transfer is done.
1565 } 1569 continue;
1566
1567 if (ft->size - ft->requested < length) {
1568 length = ft->size - ft->requested;
1569 } 1570 }
1570 1571
1571 ++ft->slots_allocated; 1572 // Allocate 1 slot to this file transfer.
1573 ft->slots_allocated++;
1572 1574
1573 uint64_t position = ft->requested; 1575 const uint16_t length = min_u64(ft->size - ft->requested, MAX_FILE_DATA_SIZE);
1576 const uint64_t position = ft->requested;
1574 ft->requested += length; 1577 ft->requested += length;
1575 1578
1576 if (m->file_reqchunk) { 1579 if (m->file_reqchunk) {
1577 (*m->file_reqchunk)(m, friendnumber, i, position, length, userdata); 1580 m->file_reqchunk(m, friendnumber, i, position, length, userdata);
1578 } 1581 }
1579 1582
1580 --free_slots; 1583 // The allocated slot is no longer free.
1584 --*free_slots;
1581 } 1585 }
1582 1586
1583 if (num == 0) { 1587 if (num == 0) {
1584 break; 1588 continue;
1585 } 1589 }
1586 } 1590 }
1591
1592 return any_active_fts;
1587} 1593}
1588 1594
1595static void do_reqchunk_filecb(Messenger *m, int32_t friendnumber, void *userdata)
1596{
1597 // We're not currently doing any file transfers.
1598 if (m->friendlist[friendnumber].num_sending_files == 0) {
1599 return;
1600 }
1601
1602 // The number of packet slots left in the sendbuffer.
1603 // This is a per friend count (CRYPTO_PACKET_BUFFER_SIZE).
1604 uint32_t free_slots = crypto_num_free_sendqueue_slots(
1605 m->net_crypto,
1606 friend_connection_crypt_connection_id(
1607 m->fr_c,
1608 m->friendlist[friendnumber].friendcon_id));
1609
1610 // We keep MIN_SLOTS_FREE slots free for other packets, otherwise file
1611 // transfers might block other traffic for a long time.
1612 free_slots = max_s32(0, (int32_t)free_slots - MIN_SLOTS_FREE);
1613
1614 bool any_active_fts = true;
1615 uint32_t loop_counter = 0;
1616 // Maximum number of outer loops below. If the client doesn't send file
1617 // chunks from within the chunk request callback handler, we never realise
1618 // that the file transfer has finished and may end up in an infinite loop.
1619 //
1620 // TODO(zoff99): Fix this to exit the loop properly when we're done
1621 // requesting all chunks for all file transfers.
1622 const uint32_t MAX_FT_LOOPS = 4;
1623
1624 while (((free_slots > 0) || loop_counter == 0) && any_active_fts && (loop_counter < MAX_FT_LOOPS)) {
1625 any_active_fts = do_all_filetransfers(m, friendnumber, userdata, &free_slots);
1626 loop_counter++;
1627 }
1628}
1629
1630
1589/* Run this when the friend disconnects. 1631/* Run this when the friend disconnects.
1590 * Kill all current file transfers. 1632 * Kill all current file transfers.
1591 */ 1633 */
1592static void break_files(const Messenger *m, int32_t friendnumber) 1634static void break_files(const Messenger *m, int32_t friendnumber)
1593{ 1635{
1594 uint32_t i;
1595
1596 // TODO(irungentoo): Inform the client which file transfers get killed with a callback? 1636 // TODO(irungentoo): Inform the client which file transfers get killed with a callback?
1597 for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { 1637 for (uint32_t i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) {
1598 if (m->friendlist[friendnumber].file_sending[i].status != FILESTATUS_NONE) { 1638 if (m->friendlist[friendnumber].file_sending[i].status != FILESTATUS_NONE) {
1599 m->friendlist[friendnumber].file_sending[i].status = FILESTATUS_NONE; 1639 m->friendlist[friendnumber].file_sending[i].status = FILESTATUS_NONE;
1600 } 1640 }
diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h
index 6bf1bce8..956f8ac5 100644
--- a/toxcore/Messenger.h
+++ b/toxcore/Messenger.h
@@ -206,7 +206,7 @@ typedef struct {
206 uint64_t last_seen_time; 206 uint64_t last_seen_time;
207 uint8_t last_connection_udp_tcp; 207 uint8_t last_connection_udp_tcp;
208 struct File_Transfers file_sending[MAX_CONCURRENT_FILE_PIPES]; 208 struct File_Transfers file_sending[MAX_CONCURRENT_FILE_PIPES];
209 unsigned int num_sending_files; 209 uint32_t num_sending_files;
210 struct File_Transfers file_receiving[MAX_CONCURRENT_FILE_PIPES]; 210 struct File_Transfers file_receiving[MAX_CONCURRENT_FILE_PIPES];
211 211
212 struct { 212 struct {
diff --git a/toxcore/util.c b/toxcore/util.c
index ff612b1a..e19808a2 100644
--- a/toxcore/util.c
+++ b/toxcore/util.c
@@ -195,3 +195,13 @@ int create_recursive_mutex(pthread_mutex_t *mutex)
195 195
196 return 0; 196 return 0;
197} 197}
198
199int32_t max_s32(int32_t a, int32_t b)
200{
201 return a > b ? a : b;
202}
203
204uint64_t min_u64(uint64_t a, uint64_t b)
205{
206 return a < b ? a : b;
207}
diff --git a/toxcore/util.h b/toxcore/util.h
index 8c86158b..a9faa863 100644
--- a/toxcore/util.h
+++ b/toxcore/util.h
@@ -65,6 +65,9 @@ int load_state(load_state_callback_func load_state_callback, Logger *log, void *
65/* Returns -1 if failed or 0 if success */ 65/* Returns -1 if failed or 0 if success */
66int create_recursive_mutex(pthread_mutex_t *mutex); 66int create_recursive_mutex(pthread_mutex_t *mutex);
67 67
68int32_t max_s32(int32_t a, int32_t b);
69uint64_t min_u64(uint64_t a, uint64_t b);
70
68#ifdef __cplusplus 71#ifdef __cplusplus
69} // extern "C" 72} // extern "C"
70#endif 73#endif