diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f04ba61..63df3aba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,6 +434,7 @@ auto_test(version) auto_test(save_compatibility) if(BUILD_TOXAV) + auto_test(conference_av) auto_test(toxav_basic) auto_test(toxav_many) endif() diff --git a/auto_tests/Makefile.inc b/auto_tests/Makefile.inc index 3a4b6c7e..98f9db13 100644 --- a/auto_tests/Makefile.inc +++ b/auto_tests/Makefile.inc @@ -57,7 +57,7 @@ AUTOTEST_LDADD = \ if BUILD_AV -TESTS += toxav_basic_test toxav_many_test +TESTS += conference_av_test toxav_basic_test toxav_many_test AUTOTEST_LDADD += libtoxav.la endif @@ -221,6 +221,10 @@ version_test_LDADD = $(AUTOTEST_LDADD) if BUILD_AV +conference_av_test_SOURCES = ../auto_tests/conference_av_test.c +conference_av_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_av_test_LDADD = $(AUTOTEST_LDADD) + toxav_basic_test_SOURCES = ../auto_tests/toxav_basic_test.c toxav_basic_test_CFLAGS = $(AUTOTEST_CFLAGS) toxav_basic_test_LDADD = $(AUTOTEST_LDADD) $(AV_LIBS) diff --git a/auto_tests/conference_av_test.c b/auto_tests/conference_av_test.c new file mode 100644 index 00000000..6d701751 --- /dev/null +++ b/auto_tests/conference_av_test.c @@ -0,0 +1,467 @@ +/* Auto Tests: Conferences AV. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "../toxav/toxav.h" +#include "check_compat.h" + +#define NUM_AV_GROUP_TOX 16 +#define NUM_AV_DISCONNECT (NUM_AV_GROUP_TOX / 2) +#define NUM_AV_DISABLE (NUM_AV_GROUP_TOX / 2) + +typedef struct State { + uint32_t index; + uint64_t clock; + + bool invited_next; + + uint32_t received_audio_peers[NUM_AV_GROUP_TOX]; + uint32_t received_audio_num; +} State; + +#include "run_auto_test.h" + +static void handle_self_connection_status( + Tox *tox, Tox_Connection connection_status, void *user_data) +{ + const State *state = (State *)user_data; + + if (connection_status != TOX_CONNECTION_NONE) { + printf("tox #%u: is now connected\n", state->index); + } else { + printf("tox #%u: is now disconnected\n", state->index); + } +} + +static void handle_friend_connection_status( + Tox *tox, uint32_t friendnumber, Tox_Connection connection_status, void *user_data) +{ + const State *state = (State *)user_data; + + if (connection_status != TOX_CONNECTION_NONE) { + printf("tox #%u: is now connected to friend %u\n", state->index, friendnumber); + } else { + printf("tox #%u: is now disconnected from friend %u\n", state->index, friendnumber); + } +} + +static void audio_callback(void *tox, uint32_t groupnumber, uint32_t peernumber, + const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t + sample_rate, void *userdata) +{ + if (samples == 0) { + return; + } + + State *state = (State *)userdata; + + for (uint32_t i = 0; i < state->received_audio_num; ++i) { + if (state->received_audio_peers[i] == peernumber) { + return; + } + } + + ck_assert(state->received_audio_num < NUM_AV_GROUP_TOX); + + state->received_audio_peers[state->received_audio_num] = peernumber; + ++state->received_audio_num; +} + +static void handle_conference_invite( + Tox *tox, uint32_t friendnumber, Tox_Conference_Type type, + const uint8_t *data, size_t length, void *user_data) +{ + const State *state = (State *)user_data; + ck_assert_msg(type == TOX_CONFERENCE_TYPE_AV, "tox #%u: wrong conference type: %d", state->index, type); + + ck_assert_msg(toxav_join_av_groupchat(tox, friendnumber, data, length, audio_callback, user_data) == 0, + "tox #%u: failed to join group", state->index); +} + +static void handle_conference_connected( + Tox *tox, uint32_t conference_number, void *user_data) +{ + State *state = (State *)user_data; + + if (state->invited_next || tox_self_get_friend_list_size(tox) <= 1) { + return; + } + + Tox_Err_Conference_Invite err; + tox_conference_invite(tox, 1, 0, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %d", state->index, err); + printf("tox #%u: invited next friend\n", state->index); + state->invited_next = true; +} + +static bool toxes_are_disconnected_from_group(uint32_t tox_count, Tox **toxes, + bool *disconnected) +{ + uint32_t num_disconnected = 0; + + for (uint32_t i = 0; i < tox_count; ++i) { + num_disconnected += disconnected[i]; + } + + for (uint32_t i = 0; i < tox_count; i++) { + if (disconnected[i]) { + continue; + } + + if (tox_conference_peer_count(toxes[i], 0, nullptr) > tox_count - num_disconnected) { + return false; + } + } + + return true; +} + +static void disconnect_toxes(uint32_t tox_count, Tox **toxes, State *state, + const bool *disconnect, const bool *exclude) +{ + /* Fake a network outage for a set of peers D by iterating only the other + * peers D' until the connections time out according to D', then iterating + * only D until the connections time out according to D. */ + + VLA(bool, disconnect_now, tox_count); + bool invert = false; + + do { + for (uint32_t i = 0; i < tox_count; ++i) { + disconnect_now[i] = exclude[i] || (invert ^ disconnect[i]); + } + + do { + for (uint32_t i = 0; i < tox_count; ++i) { + if (!disconnect_now[i]) { + tox_iterate(toxes[i], &state[i]); + state[i].clock += 1000; + } + } + + c_sleep(20); + } while (!toxes_are_disconnected_from_group(tox_count, toxes, disconnect_now)); + + invert = !invert; + } while (invert); +} + +static bool all_connected_to_group(uint32_t tox_count, Tox **toxes) +{ + for (uint32_t i = 0; i < tox_count; i++) { + if (tox_conference_peer_count(toxes[i], 0, nullptr) < tox_count) { + return false; + } + } + + return true; +} + +/** + * returns a random index at which a list of booleans is false + * (some such index is required to exist) + */ +static uint32_t random_false_index(bool *list, const uint32_t length) +{ + uint32_t index; + + do { + index = random_u32() % length; + } while (list[index]); + + return index; +} + +static bool all_got_audio(State *state, const bool *disabled) +{ + uint32_t num_disabled = 0; + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + num_disabled += disabled[i]; + } + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (disabled[i] ^ (state[i].received_audio_num + != NUM_AV_GROUP_TOX - num_disabled - 1)) { + return false; + } + } + + return true; +} + +static void reset_received_audio(Tox **toxes, State *state) +{ + for (uint32_t j = 0; j < NUM_AV_GROUP_TOX; ++j) { + state[j].received_audio_num = 0; + } +} + +#define GROUP_AV_TEST_SAMPLES 960 + +/* must have + * GROUP_AV_AUDIO_ITERATIONS - NUM_AV_GROUP_TOX >= 2^n >= GROUP_JBUF_SIZE + * for some n, to give messages time to be relayed and to let the jitter + * buffers fill up. */ +#define GROUP_AV_AUDIO_ITERATIONS (8 + NUM_AV_GROUP_TOX) + +static bool test_audio(Tox **toxes, State *state, const bool *disabled, bool quiet) +{ + if (!quiet) { + printf("testing sending and receiving audio\n"); + } + + int16_t PCM[GROUP_AV_TEST_SAMPLES]; + + reset_received_audio(toxes, state); + + for (uint32_t n = 0; n < GROUP_AV_AUDIO_ITERATIONS; n++) { + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (disabled[i]) { + continue; + } + + if (toxav_group_send_audio(toxes[i], 0, PCM, GROUP_AV_TEST_SAMPLES, 1, 48000) != 0) { + if (!quiet) { + ck_abort_msg("#%u failed to send audio", state[i].index); + } + + return false; + } + } + + iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL); + + if (all_got_audio(state, disabled)) { + return true; + } + } + + if (!quiet) { + ck_abort_msg("group failed to receive audio"); + } + + return false; +} + +static void test_eventual_audio(Tox **toxes, State *state, const bool *disabled, uint64_t timeout) +{ + uint64_t start = state[0].clock; + + while (state[0].clock < start + timeout) { + if (test_audio(toxes, state, disabled, true) + && test_audio(toxes, state, disabled, true)) { + printf("audio test successful after %d seconds\n", (int)((state[0].clock - start) / 1000)); + return; + } + } + + printf("audio seems not to be getting through: testing again with errors.\n"); + test_audio(toxes, state, disabled, false); +} + +static void do_audio(Tox **toxes, State *state, uint32_t iterations) +{ + int16_t PCM[GROUP_AV_TEST_SAMPLES]; + printf("running audio for %u iterations\n", iterations); + + for (uint32_t f = 0; f < iterations; ++f) { + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + ck_assert_msg(toxav_group_send_audio(toxes[i], 0, PCM, GROUP_AV_TEST_SAMPLES, 1, 48000) == 0, + "#%u failed to send audio", state[i].index); + iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL); + } + } +} + +// should agree with value in groupav.c +#define GROUP_JBUF_DEAD_SECONDS 4 + +#define JITTER_SETTLE_TIME (GROUP_JBUF_DEAD_SECONDS*1000 + NUM_AV_GROUP_TOX*ITERATION_INTERVAL*(GROUP_AV_AUDIO_ITERATIONS+1)) + +static void run_conference_tests(Tox **toxes, State *state) +{ + bool disabled[NUM_AV_GROUP_TOX] = {0}; + + test_audio(toxes, state, disabled, false); + + /* have everyone send audio for a bit so we can test that the audio + * sequnums dropping to 0 on restart isn't a problem */ + do_audio(toxes, state, 20); + + printf("letting random toxes timeout\n"); + bool disconnected[NUM_AV_GROUP_TOX] = {0}; + bool restarting[NUM_AV_GROUP_TOX] = {0}; + + ck_assert(NUM_AV_DISCONNECT < NUM_AV_GROUP_TOX); + + for (uint32_t i = 0; i < NUM_AV_DISCONNECT; ++i) { + uint32_t disconnect = random_false_index(disconnected, NUM_AV_GROUP_TOX); + disconnected[disconnect] = true; + + if (i < NUM_AV_DISCONNECT / 2) { + restarting[disconnect] = true; + printf("Restarting #%u\n", state[disconnect].index); + } else { + printf("Disconnecting #%u\n", state[disconnect].index); + } + } + + uint8_t *save[NUM_AV_GROUP_TOX]; + size_t save_size[NUM_AV_GROUP_TOX]; + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (restarting[i]) { + save_size[i] = tox_get_savedata_size(toxes[i]); + ck_assert_msg(save_size[i] != 0, "save is invalid size %u", (unsigned)save_size[i]); + save[i] = (uint8_t *)malloc(save_size[i]); + ck_assert_msg(save[i] != nullptr, "malloc failed"); + tox_get_savedata(toxes[i], save[i]); + tox_kill(toxes[i]); + } + } + + disconnect_toxes(NUM_AV_GROUP_TOX, toxes, state, disconnected, restarting); + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (restarting[i]) { + struct Tox_Options *const options = tox_options_new(nullptr); + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + tox_options_set_savedata_data(options, save[i], save_size[i]); + toxes[i] = tox_new_log(options, nullptr, &state[i].index); + tox_options_free(options); + free(save[i]); + + set_mono_time_callback(toxes[i], &state[i]); + } + } + + printf("reconnecting toxes\n"); + + do { + iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL); + } while (!all_connected_to_group(NUM_AV_GROUP_TOX, toxes)); + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (restarting[i]) { + ck_assert_msg(toxav_groupchat_enable_av(toxes[i], 0, audio_callback, &state[i]) == 0, + "#%u failed to re-enable av", state[i].index); + } + } + + printf("testing audio\n"); + + /* Allow time for the jitter buffers to reset and for the group to become + * connected enough for lossy messages to get through + * (all_connected_to_group() only checks lossless connectivity, which is a + * looser condition). */ + test_eventual_audio(toxes, state, disabled, JITTER_SETTLE_TIME + NUM_AV_GROUP_TOX * 1000); + + printf("testing disabling av\n"); + + ck_assert(NUM_AV_DISABLE < NUM_AV_GROUP_TOX); + + for (uint32_t i = 0; i < NUM_AV_DISABLE; ++i) { + uint32_t disable = random_false_index(disabled, NUM_AV_GROUP_TOX); + disabled[disable] = true; + printf("Disabling #%u\n", state[disable].index); + ck_assert_msg(toxav_groupchat_enable_av(toxes[disable], 0, audio_callback, &state[disable]) != 0, + "#%u could enable already enabled av!", state[i].index); + ck_assert_msg(toxav_groupchat_disable_av(toxes[disable], 0) == 0, + "#%u failed to disable av", state[i].index); + } + + // Run test without error to clear out messages from now-disabled peers. + test_audio(toxes, state, disabled, true); + + printf("testing audio with some peers having disabled their av\n"); + test_audio(toxes, state, disabled, false); + + for (uint32_t i = 0; i < NUM_AV_DISABLE; ++i) { + if (!disabled[i]) { + continue; + } + + disabled[i] = false; + ck_assert_msg(toxav_groupchat_disable_av(toxes[i], 0) != 0, + "#%u could disable already disabled av!", state[i].index); + ck_assert_msg(toxav_groupchat_enable_av(toxes[i], 0, audio_callback, &state[i]) == 0, + "#%u failed to re-enable av", state[i].index); + } + + printf("testing audio after re-enabling all av\n"); + test_eventual_audio(toxes, state, disabled, JITTER_SETTLE_TIME); +} + +static void test_groupav(Tox **toxes, State *state) +{ + const time_t test_start_time = time(nullptr); + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + tox_callback_self_connection_status(toxes[i], &handle_self_connection_status); + tox_callback_friend_connection_status(toxes[i], &handle_friend_connection_status); + tox_callback_conference_invite(toxes[i], &handle_conference_invite); + tox_callback_conference_connected(toxes[i], &handle_conference_connected); + } + + ck_assert_msg(toxav_add_av_groupchat(toxes[0], audio_callback, &state[0]) != UINT32_MAX, "failed to create group"); + printf("tox #%u: inviting its first friend\n", state[0].index); + ck_assert_msg(tox_conference_invite(toxes[0], 0, 0, nullptr) != 0, "failed to invite friend"); + state[0].invited_next = true; + + + printf("waiting for invitations to be made\n"); + uint32_t invited_count = 0; + + do { + iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL); + + invited_count = 0; + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + invited_count += state[i].invited_next; + } + } while (invited_count != NUM_AV_GROUP_TOX - 1); + + uint64_t pregroup_clock = state[0].clock; + printf("waiting for all toxes to be in the group\n"); + uint32_t fully_connected_count = 0; + + do { + fully_connected_count = 0; + iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL); + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + Tox_Err_Conference_Peer_Query err; + uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, &err); + + if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { + peer_count = 0; + } + + fully_connected_count += peer_count == NUM_AV_GROUP_TOX; + } + } while (fully_connected_count != NUM_AV_GROUP_TOX); + + printf("group connected, took %d seconds\n", (int)((state[0].clock - pregroup_clock) / 1000)); + + run_conference_tests(toxes, state); + + printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + run_auto_test(NUM_AV_GROUP_TOX, test_groupav, true); + return 0; +} diff --git a/auto_tests/conference_test.c b/auto_tests/conference_test.c index 349c2905..5f08b823 100644 --- a/auto_tests/conference_test.c +++ b/auto_tests/conference_test.c @@ -8,11 +8,8 @@ #include #include #include +#include -#include "../testing/misc_tools.h" -#include "../toxcore/crypto_core.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" #include "check_compat.h" #define NUM_GROUP_TOX 16 @@ -91,7 +88,7 @@ static void handle_conference_connected( state->invited_next = true; } -static uint16_t num_recv; +static uint32_t num_recv; static void handle_conference_message( Tox *tox, uint32_t groupnumber, uint32_t peernumber, Tox_Message_Type type, @@ -102,15 +99,21 @@ static void handle_conference_message( } } -static bool toxes_are_disconnected_from_group(uint32_t tox_count, Tox **toxes, int disconnected_count, +static bool toxes_are_disconnected_from_group(uint32_t tox_count, Tox **toxes, bool *disconnected) { + uint32_t num_disconnected = 0; + + for (uint32_t i = 0; i < tox_count; ++i) { + num_disconnected += disconnected[i]; + } + for (uint32_t i = 0; i < tox_count; i++) { if (disconnected[i]) { continue; } - if (tox_conference_peer_count(toxes[i], 0, nullptr) > tox_count - NUM_DISCONNECT) { + if (tox_conference_peer_count(toxes[i], 0, nullptr) > tox_count - num_disconnected) { return false; } } @@ -118,6 +121,36 @@ static bool toxes_are_disconnected_from_group(uint32_t tox_count, Tox **toxes, i return true; } +static void disconnect_toxes(uint32_t tox_count, Tox **toxes, State *state, + const bool *disconnect, const bool *exclude) +{ + /* Fake a network outage for a set of peers D by iterating only the other + * peers D' until the connections time out according to D', then iterating + * only D until the connections time out according to D. */ + + VLA(bool, disconnect_now, tox_count); + bool invert = false; + + do { + for (uint32_t i = 0; i < tox_count; ++i) { + disconnect_now[i] = exclude[i] || (invert ^ disconnect[i]); + } + + do { + for (uint32_t i = 0; i < tox_count; ++i) { + if (!disconnect_now[i]) { + tox_iterate(toxes[i], &state[i]); + state[i].clock += 1000; + } + } + + c_sleep(20); + } while (!toxes_are_disconnected_from_group(tox_count, toxes, disconnect_now)); + + invert = !invert; + } while (invert); +} + static bool all_connected_to_group(uint32_t tox_count, Tox **toxes) { for (uint32_t i = 0; i < tox_count; i++) { @@ -131,8 +164,8 @@ static bool all_connected_to_group(uint32_t tox_count, Tox **toxes) static bool names_propagated(uint32_t tox_count, Tox **toxes, State *state) { - for (uint16_t i = 0; i < tox_count; ++i) { - for (uint16_t j = 0; j < tox_count; ++j) { + for (uint32_t i = 0; i < tox_count; ++i) { + for (uint32_t j = 0; j < tox_count; ++j) { const size_t len = tox_conference_peer_get_name_size(toxes[i], 0, j, nullptr); if (len != NAMELEN) { @@ -145,9 +178,10 @@ static bool names_propagated(uint32_t tox_count, Tox **toxes, State *state) } -/* returns a random index at which a list of booleans is false +/** + * returns a random index at which a list of booleans is false * (some such index is required to exist) - * */ + */ static uint32_t random_false_index(bool *list, const uint32_t length) { uint32_t index; @@ -171,7 +205,7 @@ static void run_conference_tests(Tox **toxes, State *state) ck_assert(NUM_DISCONNECT < NUM_GROUP_TOX); - for (uint16_t i = 0; i < NUM_DISCONNECT; ++i) { + for (uint32_t i = 0; i < NUM_DISCONNECT; ++i) { uint32_t disconnect = random_false_index(disconnected, NUM_GROUP_TOX); disconnected[disconnect] = true; @@ -186,7 +220,7 @@ static void run_conference_tests(Tox **toxes, State *state) uint8_t *save[NUM_GROUP_TOX]; size_t save_size[NUM_GROUP_TOX]; - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { if (restarting[i]) { save_size[i] = tox_get_savedata_size(toxes[i]); ck_assert_msg(save_size[i] != 0, "save is invalid size %u", (unsigned)save_size[i]); @@ -197,18 +231,9 @@ static void run_conference_tests(Tox **toxes, State *state) } } - do { - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - if (!disconnected[i]) { - tox_iterate(toxes[i], &state[i]); - state[i].clock += 1000; - } - } + disconnect_toxes(NUM_GROUP_TOX, toxes, state, disconnected, restarting); - c_sleep(20); - } while (!toxes_are_disconnected_from_group(NUM_GROUP_TOX, toxes, NUM_DISCONNECT, disconnected)); - - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { if (restarting[i]) { struct Tox_Options *const options = tox_options_new(nullptr); tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); @@ -216,13 +241,15 @@ static void run_conference_tests(Tox **toxes, State *state) toxes[i] = tox_new_log(options, nullptr, &state[i].index); tox_options_free(options); free(save[i]); + + set_mono_time_callback(toxes[i], &state[i]); } } if (check_name_change_propagation) { printf("changing names\n"); - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { char name[NAMELEN + 1]; snprintf(name, NAMELEN + 1, NEW_NAME_FORMAT_STR, state[i].index); tox_self_set_name(toxes[i], (const uint8_t *)name, NAMELEN, nullptr); @@ -237,7 +264,7 @@ static void run_conference_tests(Tox **toxes, State *state) printf("running conference tests\n"); - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { tox_callback_conference_message(toxes[i], &handle_conference_message); iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); @@ -259,8 +286,8 @@ static void run_conference_tests(Tox **toxes, State *state) ck_assert_msg(num_recv == NUM_GROUP_TOX, "failed to recv group messages"); if (check_name_change_propagation) { - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - for (uint16_t j = 0; j < NUM_GROUP_TOX; ++j) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t j = 0; j < NUM_GROUP_TOX; ++j) { uint8_t name[NAMELEN]; tox_conference_peer_get_name(toxes[i], 0, j, name, nullptr); /* Note the toxes will have been reordered */ @@ -270,14 +297,14 @@ static void run_conference_tests(Tox **toxes, State *state) } } - for (uint16_t k = NUM_GROUP_TOX; k != 0 ; --k) { + for (uint32_t k = NUM_GROUP_TOX; k != 0 ; --k) { tox_conference_delete(toxes[k - 1], 0, nullptr); for (uint8_t j = 0; j < 10 || j < NUM_GROUP_TOX; ++j) { iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); } - for (uint16_t i = 0; i < k - 1; ++i) { + for (uint32_t i = 0; i < k - 1; ++i) { uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, nullptr); ck_assert_msg(peer_count == (k - 1), "\n\tBad number of group peers (post check)." "\n\t\t\tExpected: %u but tox_instance(%u) only has: %u\n\n", @@ -290,7 +317,7 @@ static void test_many_group(Tox **toxes, State *state) { const time_t test_start_time = time(nullptr); - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { tox_callback_self_connection_status(toxes[i], &handle_self_connection_status); tox_callback_friend_connection_status(toxes[i], &handle_friend_connection_status); tox_callback_conference_invite(toxes[i], &handle_conference_invite); @@ -310,21 +337,21 @@ static void test_many_group(Tox **toxes, State *state) printf("waiting for invitations to be made\n"); - uint16_t invited_count = 0; + uint32_t invited_count = 0; do { iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); invited_count = 0; - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { invited_count += state[i].invited_next; } } while (invited_count != NUM_GROUP_TOX - 1); uint64_t pregroup_clock = state[0].clock; printf("waiting for all toxes to be in the group\n"); - uint16_t fully_connected_count = 0; + uint32_t fully_connected_count = 0; do { fully_connected_count = 0; @@ -332,7 +359,7 @@ static void test_many_group(Tox **toxes, State *state) iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { Tox_Err_Conference_Peer_Query err; uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, &err); @@ -353,7 +380,7 @@ static void test_many_group(Tox **toxes, State *state) fflush(stdout); } while (fully_connected_count != NUM_GROUP_TOX); - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, nullptr); ck_assert_msg(peer_count == NUM_GROUP_TOX, "\n\tBad number of group peers (pre check)." diff --git a/auto_tests/run_auto_test.h b/auto_tests/run_auto_test.h index 4f2dc278..47d9dc4a 100644 --- a/auto_tests/run_auto_test.h +++ b/auto_tests/run_auto_test.h @@ -48,6 +48,15 @@ static uint64_t get_state_clock_callback(Mono_Time *mono_time, void *user_data) return state->clock; } +static void set_mono_time_callback(Tox *tox, State *state) +{ + // TODO(iphydf): Don't rely on toxcore internals. + Mono_Time *mono_time = ((Messenger *)tox)->mono_time; + + state->clock = current_time_monotonic(mono_time); + mono_time_set_current_time_callback(mono_time, get_state_clock_callback, state); +} + static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *state), bool chain) { printf("initialising %u toxes\n", tox_count); @@ -59,11 +68,7 @@ static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *stat toxes[i] = tox_new_log(nullptr, nullptr, &state[i].index); ck_assert_msg(toxes[i], "failed to create %u tox instances", i + 1); - // TODO(iphydf): Don't rely on toxcore internals. - Mono_Time *mono_time = (*(Messenger **)toxes[i])->mono_time; - - state[i].clock = current_time_monotonic(mono_time); - mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &state[i]); + set_mono_time_callback(toxes[i], &state[i]); } if (chain) { diff --git a/toxav/groupav.c b/toxav/groupav.c index 4c16d1de..f49848de 100644 --- a/toxav/groupav.c +++ b/toxav/groupav.c @@ -433,14 +433,19 @@ static int handle_group_audio_packet(void *object, uint32_t groupnumber, uint32_ return 0; } -/* Convert groupchat to an A/V groupchat. +/* Enable A/V in a groupchat. * * return 0 on success. * return -1 on failure. */ -static int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t groupnumber, - audio_data_cb *audio_callback, void *userdata) +int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t groupnumber, + audio_data_cb *audio_callback, void *userdata) { + if (group_get_type(g_c, groupnumber) != GROUPCHAT_TYPE_AV + || group_get_object(g_c, groupnumber) != nullptr) { + return -1; + } + Group_AV *group_av = new_group_av(log, tox, g_c, audio_callback, userdata); if (group_av == nullptr) { @@ -455,10 +460,52 @@ static int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, ui return -1; } + int numpeers = group_number_peers(g_c, groupnumber, false); + + for (uint32_t i = 0; i < numpeers; ++i) { + group_av_peer_new(group_av, groupnumber, i); + } + group_lossy_packet_registerhandler(g_c, GROUP_AUDIO_PACKET_ID, &handle_group_audio_packet); return 0; } +/* Disable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + */ +int groupchat_disable_av(Group_Chats *g_c, uint32_t groupnumber) +{ + if (group_get_type(g_c, groupnumber) != GROUPCHAT_TYPE_AV) { + return -1; + } + + Group_AV *group_av = (Group_AV *)group_get_object(g_c, groupnumber); + + if (group_av == nullptr) { + return -1; + } + + int numpeers = group_number_peers(g_c, groupnumber, false); + + for (uint32_t i = 0; i < numpeers; ++i) { + group_av_peer_delete(group_av, groupnumber, group_peer_get_object(g_c, groupnumber, i)); + group_peer_set_object(g_c, groupnumber, i, nullptr); + } + + kill_group_av(group_av); + + if (group_set_object(g_c, groupnumber, nullptr) == -1 + || callback_groupchat_peer_new(g_c, groupnumber, nullptr) == -1 + || callback_groupchat_peer_delete(g_c, groupnumber, nullptr) == -1 + || callback_groupchat_delete(g_c, groupnumber, nullptr) == -1) { + return -1; + } + + return 0; +} + /* Create a new toxav group. * * return group number on success. diff --git a/toxav/groupav.h b/toxav/groupav.h index a65921a4..45ff1d60 100644 --- a/toxav/groupav.h +++ b/toxav/groupav.h @@ -59,5 +59,19 @@ int join_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t fr int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate); +/* Enable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + */ +int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t groupnumber, + audio_data_cb *audio_callback, void *userdata); + +/* Disable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + */ +int groupchat_disable_av(Group_Chats *g_c, uint32_t groupnumber); #endif // C_TOXCORE_TOXAV_GROUPAV_H diff --git a/toxav/toxav.api.h b/toxav/toxav.api.h index 14c6a8e1..84c006b7 100644 --- a/toxav/toxav.api.h +++ b/toxav/toxav.api.h @@ -654,6 +654,27 @@ int toxav_join_av_groupchat(Tox *tox, uint32_t friendnumber, const uint8_t *data int toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate); +/* Enable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + * + * Audio data callback format (same as the one for toxav_add_av_groupchat()): + * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) + * + * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). + */ +int toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, + void (*audio_callback)(void *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), + void *userdata); + +/* Disable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + */ +int toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber); + #ifdef __cplusplus } #endif diff --git a/toxav/toxav.h b/toxav/toxav.h index bc634e91..6c1ea093 100644 --- a/toxav/toxav.h +++ b/toxav/toxav.h @@ -782,6 +782,27 @@ int toxav_join_av_groupchat(Tox *tox, uint32_t friendnumber, const uint8_t *data int toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate); +/* Enable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + * + * Audio data callback format (same as the one for toxav_add_av_groupchat()): + * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) + * + * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). + */ +int toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, + void (*audio_callback)(void *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), + void *userdata); + +/* Disable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + */ +int toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber); + #ifdef __cplusplus } #endif diff --git a/toxav/toxav_old.c b/toxav/toxav_old.c index e9850973..af9980b0 100644 --- a/toxav/toxav_old.c +++ b/toxav/toxav_old.c @@ -80,3 +80,32 @@ int toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t *pcm, u Messenger *m = *(Messenger **)tox; return group_send_audio(m->conferences_object, groupnumber, pcm, samples, channels, sample_rate); } + +/* Enable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + * + * Audio data callback format (same as the one for toxav_add_av_groupchat()): + * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) + * + * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). + */ +int toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, audio_data_cb *audio_callback, void *userdata) +{ + // TODO(iphydf): Don't rely on toxcore internals. + Messenger *m = *(Messenger **)tox; + return groupchat_enable_av(m->log, tox, m->conferences_object, groupnumber, audio_callback, userdata); +} + +/* Disable A/V in a groupchat. + * + * return 0 on success. + * return -1 on failure. + */ +int toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber) +{ + // TODO(iphydf): Don't rely on toxcore internals. + Messenger *m = *(Messenger **)tox; + return groupchat_disable_av(m->conferences_object, groupnumber); +} diff --git a/toxcore/group.c b/toxcore/group.c index 101c1196..20ee5459 100644 --- a/toxcore/group.c +++ b/toxcore/group.c @@ -539,9 +539,7 @@ static int note_peer_active(Group_Chats *g_c, uint32_t groupnumber, uint16_t pee ++g->numpeers; - if (!delete_frozen(g, frozen_index)) { - return -1; - } + delete_frozen(g, frozen_index); if (g_c->peer_list_changed_callback) { g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); @@ -774,6 +772,7 @@ static int freeze_peer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, v g->frozen = temp; g->frozen[g->numfrozen] = g->group[peer_index]; + g->frozen[g->numfrozen].object = nullptr; ++g->numfrozen; return delpeer(g_c, groupnumber, peer_index, userdata, true); @@ -2831,6 +2830,12 @@ static unsigned int lossy_packet_not_received(const Group_c *g, int peer_index, } +/* Does this group type make use of lossy packets? */ +static bool type_uses_lossy(uint8_t type) +{ + return (type == GROUPCHAT_TYPE_AV); +} + static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) { Group_Chats *g_c = (Group_Chats *)object; @@ -2857,6 +2862,10 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin return -1; } + if (!type_uses_lossy(g->type)) { + return -1; + } + const int index = friend_in_close(g, friendcon_id); if (index == -1) { @@ -2883,6 +2892,8 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin ++lossy_data; --lossy_length; + send_lossy_all_close(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); + if (g_c->lossy_packethandlers[message_id].function) { if (g_c->lossy_packethandlers[message_id].function(g->object, groupnumber, peer_index, g->group[peer_index].object, lossy_data, lossy_length) == -1) { @@ -2892,7 +2903,6 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin return -1; } - send_lossy_all_close(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); return 0; } @@ -2934,7 +2944,7 @@ int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, int peer return 0; } -/* Return the object tide to the group chat previously set by group_set_object. +/* Return the object tied to the group chat previously set by group_set_object. * * return NULL on failure. * return object on success. @@ -2950,7 +2960,7 @@ void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber) return g->object; } -/* Return the object tide to the group chat peer previously set by group_peer_set_object. +/* Return the object tied to the group chat peer previously set by group_peer_set_object. * * return NULL on failure. * return object on success.