From db07bda7f7b1ab7f5f219a9ed3d7f732b7da66b0 Mon Sep 17 00:00:00 2001 From: "zugz (tox)" Date: Sun, 1 Mar 2020 00:00:00 +0000 Subject: Add "member" invite response This allows invitations to work in the case that the invitee is already in the group, which can happen if the group becomes split. Such an invitation is automatically accepted, leading to the peers becoming connected in the group and sharing peer lists. --- CMakeLists.txt | 1 + auto_tests/Makefile.inc | 5 + auto_tests/conference_double_invite_test.c | 4 +- auto_tests/conference_invite_merge_test.c | 244 +++++++++++++++++++++++++++++ toxcore/group.c | 128 ++++++++++----- 5 files changed, 344 insertions(+), 38 deletions(-) create mode 100644 auto_tests/conference_invite_merge_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 81a41e7f..355fd103 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -397,6 +397,7 @@ auto_test(TCP) auto_test(bootstrap) auto_test(conference) auto_test(conference_double_invite) +auto_test(conference_invite_merge) auto_test(conference_peer_nick) auto_test(conference_simple) auto_test(conference_two) diff --git a/auto_tests/Makefile.inc b/auto_tests/Makefile.inc index fcaeff5b..983c83d3 100644 --- a/auto_tests/Makefile.inc +++ b/auto_tests/Makefile.inc @@ -3,6 +3,7 @@ if BUILD_TESTS TESTS = \ bootstrap_test \ conference_double_invite_test \ + conference_invite_merge_test \ conference_peer_nick_test \ conference_simple_test \ conference_test \ @@ -73,6 +74,10 @@ conference_double_invite_test_SOURCES = ../auto_tests/conference_double_invite_t conference_double_invite_test_CFLAGS = $(AUTOTEST_CFLAGS) conference_double_invite_test_LDADD = $(AUTOTEST_LDADD) +conference_invite_merge_test_SOURCES = ../auto_tests/conference_invite_merge_test.c +conference_invite_merge_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_invite_merge_test_LDADD = $(AUTOTEST_LDADD) + conference_peer_nick_test_SOURCES = ../auto_tests/conference_peer_nick_test.c conference_peer_nick_test_CFLAGS = $(AUTOTEST_CFLAGS) conference_peer_nick_test_LDADD = $(AUTOTEST_LDADD) diff --git a/auto_tests/conference_double_invite_test.c b/auto_tests/conference_double_invite_test.c index 17124f1c..c9e64e5b 100644 --- a/auto_tests/conference_double_invite_test.c +++ b/auto_tests/conference_double_invite_test.c @@ -28,6 +28,8 @@ static void handle_conference_invite( state->index, friend_number, type, (unsigned)length); fprintf(stderr, "tox%u joining conference\n", state->index); + ck_assert_msg(!state->joined, "invitation callback generated for already joined conference"); + if (friend_number != -1) { Tox_Err_Conference_Join err; state->conference = tox_conference_join(tox, friend_number, cookie, length, &err); @@ -71,7 +73,7 @@ static void conference_double_invite_test(Tox **toxes, State *state) fprintf(stderr, "Invitations accepted\n"); - // Invite one more time, resulting in friend -1 inviting tox1 (toxes[1]). + fprintf(stderr, "Sending second invitation; should be ignored\n"); tox_conference_invite(toxes[0], 0, state[0].conference, nullptr); iterate_all_wait(2, toxes, state, ITERATION_INTERVAL); diff --git a/auto_tests/conference_invite_merge_test.c b/auto_tests/conference_invite_merge_test.c new file mode 100644 index 00000000..e7ec499c --- /dev/null +++ b/auto_tests/conference_invite_merge_test.c @@ -0,0 +1,244 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +typedef struct State { + uint32_t index; + uint64_t clock; + + size_t save_size; + uint8_t *save_state; + bool alive; + + bool connected; + uint32_t conference; +} State; + +#define NUM_INVITE_MERGE_TOX 5 + +#include "run_auto_test.h" + +static void handle_conference_invite( + Tox *tox, uint32_t friend_number, Tox_Conference_Type type, + const uint8_t *cookie, size_t length, void *user_data) +{ + State *state = (State *)user_data; + + if (friend_number != -1) { + Tox_Err_Conference_Join err; + state->conference = tox_conference_join(tox, friend_number, cookie, length, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, + "attempting to join the conference returned with an error: %d", err); + fprintf(stderr, "#%u accepted invite to conference %u\n", state->index, state->conference); + } +} + +static void handle_conference_connected( + Tox *tox, uint32_t conference_number, void *user_data) +{ + State *state = (State *)user_data; + fprintf(stderr, "#%u connected to conference %u\n", state->index, state->conference); + state->connected = true; +} + +static void iterate_alive(Tox **toxes, State *state) +{ + for (uint32_t i = 0; i < NUM_INVITE_MERGE_TOX; i++) { + if (!state[i].alive) { + continue; + } + + tox_iterate(toxes[i], &state[i]); + state[i].clock += ITERATION_INTERVAL; + } + + c_sleep(20); +} + +static void save(Tox **toxes, State *state, uint32_t n) +{ + fprintf(stderr, "Saving #%u\n", state[n].index); + + if (state[n].save_state != nullptr) { + free(state[n].save_state); + } + + state[n].save_size = tox_get_savedata_size(toxes[n]); + state[n].save_state = (uint8_t *)malloc(state[n].save_size); + ck_assert_msg(state[n].save_state != nullptr, "malloc failed"); + tox_get_savedata(toxes[n], state[n].save_state); +} + +static void kill(Tox **toxes, State *state, uint32_t n) +{ + fprintf(stderr, "Killing #%u\n", state[n].index); + state[n].alive = false; + tox_kill(toxes[n]); +} + +static void reload(Tox **toxes, State *state, uint32_t n) +{ + if (state[n].alive) { + state[n].alive = false; + tox_kill(toxes[n]); + } + + fprintf(stderr, "Reloading #%u\n", state[n].index); + ck_assert(state[n].save_state != nullptr); + + 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, state[n].save_state, state[n].save_size); + toxes[n] = tox_new_log(options, nullptr, &state[n].index); + tox_options_free(options); + + set_mono_time_callback(toxes[n], &state[n]); + state[n].alive = true; +} + +static void wait_connected(Tox **toxes, State *state, uint32_t n, uint32_t friendnumber) +{ + do { + iterate_alive(toxes, state); + } while (tox_friend_get_connection_status(toxes[n], friendnumber, nullptr) == TOX_CONNECTION_NONE); +} + +static void do_invite(Tox **toxes, State *state, uint32_t inviter, uint32_t invitee, uint32_t friendnum) +{ + fprintf(stderr, "#%u inviting #%u\n", state[inviter].index, state[invitee].index); + + Tox_Err_Conference_Invite err; + tox_conference_invite(toxes[inviter], friendnum, state[inviter].conference, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, + "#%u attempting to invite #%u (friendnumber %u) returned with an error: %d", state[inviter].index, state[invitee].index, + friendnum, err); + + do { + iterate_alive(toxes, state); + } while (!state[invitee].connected); +} + +static bool group_complete(Tox **toxes, State *state) +{ + int c = -1, size = 0; + + for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { + if (!state[i].alive) { + continue; + } + + const int ct = tox_conference_peer_count(toxes[i], state[i].conference, nullptr); + + if (c == -1) { + c = ct; + } else if (c != ct) { + return false; + } + + ++size; + } + + return (c == size); +} + +static void wait_group_complete(Tox **toxes, State *state) +{ + do { + iterate_alive(toxes, state); + } while (!group_complete(toxes, state)); +} + +static void conference_invite_merge_test(Tox **toxes, State *state) +{ + // Test that an explicit invite between peers in different connected + // components will cause a split group to merge + + for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { + tox_callback_conference_invite(toxes[i], handle_conference_invite); + tox_callback_conference_connected(toxes[i], &handle_conference_connected); + state[i].alive = true; + state[i].save_state = nullptr; + } + + { + // Create new conference, tox 2 is the founder. + Tox_Err_Conference_New err; + state[2].conference = tox_conference_new(toxes[2], &err); + state[2].connected = true; + ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, + "attempting to create a new conference returned with an error: %d", err); + fprintf(stderr, "Created conference: index=%u\n", state[2].conference); + } + + save(toxes, state, 2); + + do_invite(toxes, state, 2, 1, 0); + do_invite(toxes, state, 1, 0, 0); + + save(toxes, state, 1); + kill(toxes, state, 1); + + do { + iterate_alive(toxes, state); + } while (tox_conference_peer_count(toxes[2], state[2].conference, nullptr) != 1); + + do_invite(toxes, state, 2, 3, 1); + do_invite(toxes, state, 3, 4, 1); + + kill(toxes, state, 2); + + reload(toxes, state, 1); + + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(toxes[1], public_key); + tox_friend_add_norequest(toxes[3], public_key, nullptr); + tox_self_get_public_key(toxes[3], public_key); + tox_friend_add_norequest(toxes[1], public_key, nullptr); + wait_connected(toxes, state, 1, 2); + + do_invite(toxes, state, 1, 3, 2); + + fprintf(stderr, "Waiting for group to merge\n"); + + wait_group_complete(toxes, state); + + fprintf(stderr, "Group merged\n"); + + reload(toxes, state, 2); + wait_connected(toxes, state, 2, 0); + do_invite(toxes, state, 2, 1, 0); + + fprintf(stderr, "Waiting for #2 to rejoin\n"); + + wait_group_complete(toxes, state); + + kill(toxes, state, 2); + wait_group_complete(toxes, state); + reload(toxes, state, 2); + wait_connected(toxes, state, 2, 0); + wait_connected(toxes, state, 1, 1); + + do_invite(toxes, state, 1, 2, 1); + + fprintf(stderr, "Waiting for #2 to rejoin\n"); + + wait_group_complete(toxes, state); + + for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { + if (state[i].save_state != nullptr) { + free(state[i].save_state); + } + } +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + run_auto_test(NUM_INVITE_MERGE_TOX, conference_invite_merge_test, true); + return 0; +} diff --git a/toxcore/group.c b/toxcore/group.c index 50f6f2fe..b4248fd8 100644 --- a/toxcore/group.c +++ b/toxcore/group.c @@ -39,11 +39,13 @@ typedef enum Group_Message_Id { typedef enum Invite_Id { INVITE_ID = 0, - INVITE_RESPONSE_ID = 1, + INVITE_ACCEPT_ID = 1, + INVITE_MEMBER_ID = 2, } Invite_Id; #define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) -#define INVITE_RESPONSE_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH) +#define INVITE_ACCEPT_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH) +#define INVITE_MEMBER_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH + sizeof(uint16_t)) #define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) @@ -1512,6 +1514,9 @@ static bool try_send_rejoin(Group_Chats *g_c, Group_c *g, const uint8_t *real_pk static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num); +static bool send_invite_response(Group_Chats *g_c, int groupnumber, uint32_t friendnumber, const uint8_t *data, + uint16_t length); + /* Join a group (we need to have been invited first.) * * expected_type is the groupchat type we expect the chat we are joining to @@ -1553,35 +1558,65 @@ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_typ Group_c *g = &g_c->chats[groupnumber]; - const uint16_t group_num = net_htons(groupnumber); g->status = GROUPCHAT_STATUS_VALID; memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); - uint8_t response[INVITE_RESPONSE_PACKET_SIZE]; - response[0] = INVITE_RESPONSE_ID; - memcpy(response + 1, &group_num, sizeof(uint16_t)); - memcpy(response + 1 + sizeof(uint16_t), data, sizeof(uint16_t) + 1 + GROUP_ID_LENGTH); + if (!send_invite_response(g_c, groupnumber, friendnumber, data, length)) { + g->status = GROUPCHAT_STATUS_NONE; + return -6; + } + + return groupnumber; +} + +static bool send_invite_response(Group_Chats *g_c, int groupnumber, uint32_t friendnumber, const uint8_t *data, + uint16_t length) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + const bool member = (g->status == GROUPCHAT_STATUS_CONNECTED); + + VLA(uint8_t, response, member ? INVITE_MEMBER_PACKET_SIZE : INVITE_ACCEPT_PACKET_SIZE); + response[0] = member ? INVITE_MEMBER_ID : INVITE_ACCEPT_ID; + net_pack_u16(response + 1, groupnumber); + memcpy(response + 1 + sizeof(uint16_t), data, length); + + if (member) { + net_pack_u16(response + 1 + sizeof(uint16_t) + length, g->peer_number); + } + + if (!send_conference_invite_packet(g_c->m, friendnumber, response, SIZEOF_VLA(response))) { + return false; + } - if (send_conference_invite_packet(g_c->m, friendnumber, response, sizeof(response))) { - uint16_t other_groupnum; - memcpy(&other_groupnum, data, sizeof(other_groupnum)); - other_groupnum = net_ntohs(other_groupnum); + if (!member) { g->type = data[sizeof(uint16_t)]; memcpy(g->id, data + sizeof(uint16_t) + 1, GROUP_ID_LENGTH); - const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, - GROUPCHAT_CONNECTION_REASON_INTRODUCER, 1); + } - if (connection_index != -1) { - g->connections[connection_index].group_number = other_groupnum; - g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE; - } + uint16_t other_groupnum; + net_unpack_u16(data, &other_groupnum); - send_peer_query(g_c, friendcon_id, other_groupnum); - return groupnumber; + const int friendcon_id = getfriendcon_id(g_c->m, friendnumber); + + if (friendcon_id == -1) { + return false; + } + + const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, 1); + + if (member) { + add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCING, 0); } - g->status = GROUPCHAT_STATUS_NONE; - return -6; + if (connection_index != -1) { + g->connections[connection_index].group_number = other_groupnum; + g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE; + } + + send_peer_query(g_c, friendcon_id, other_groupnum); + + return true; } /* Set handlers for custom lossy packets. */ @@ -1911,19 +1946,28 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con } return; + } else { + Group_c *g = get_group_c(g_c, groupnumber); + + if (g && g->status == GROUPCHAT_STATUS_CONNECTED) { + send_invite_response(g_c, groupnumber, friendnumber, invite_data, invite_length); + } } break; } - case INVITE_RESPONSE_ID: { - if (length != INVITE_RESPONSE_PACKET_SIZE) { + case INVITE_ACCEPT_ID: + case INVITE_MEMBER_ID: { + const bool member = (data[0] == INVITE_MEMBER_ID); + + if (length != (member ? INVITE_MEMBER_PACKET_SIZE : INVITE_ACCEPT_PACKET_SIZE)) { return; } uint16_t other_groupnum, groupnum; - memcpy(&groupnum, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); - groupnum = net_ntohs(groupnum); + net_unpack_u16(data + 1, &other_groupnum); + net_unpack_u16(data + 1 + sizeof(uint16_t), &groupnum); Group_c *g = get_group_c(g_c, groupnum); @@ -1939,24 +1983,28 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con return; } - /* TODO(irungentoo): what if two people enter the group at the same time and - are given the same peer_number by different nodes? */ - uint16_t peer_number = random_u16(); + uint16_t peer_number; - unsigned int tries = 0; - - while (get_peer_index(g, peer_number) != -1 || get_frozen_index(g, peer_number) != -1) { + if (member) { + net_unpack_u16(data + 1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH, &peer_number); + } else { + /* TODO(irungentoo): what if two people enter the group at the + * same time and are given the same peer_number by different + * nodes? */ peer_number = random_u16(); - ++tries; - if (tries > 32) { - return; + unsigned int tries = 0; + + while (get_peer_index(g, peer_number) != -1 || get_frozen_index(g, peer_number) != -1) { + peer_number = random_u16(); + ++tries; + + if (tries > 32) { + return; + } } } - memcpy(&other_groupnum, data + 1, sizeof(uint16_t)); - other_groupnum = net_ntohs(other_groupnum); - const int friendcon_id = getfriendcon_id(m, friendnumber); if (friendcon_id == -1) { @@ -1971,12 +2019,18 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCING, 1); + if (member) { + add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, 0); + send_peer_query(g_c, friendcon_id, other_groupnum); + } + if (connection_index != -1) { g->connections[connection_index].group_number = other_groupnum; g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE; } group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); + break; } -- cgit v1.2.3