From 1b2322284f0b688af3a349fe4331be15a565084c Mon Sep 17 00:00:00 2001 From: zugz Date: Wed, 25 Jul 2018 08:43:48 +0100 Subject: [PATCH] Add mechanism for recovering from disconnections in conferences * add freezing and unfreezing of peers * add rejoin packet * revise handling of temporary invited connections * rename "peer kill" packet to "peer leave" packet * test rejoining in conference test * use custom clock in conference test --- auto_tests/conference_double_invite_test.c | 2 +- auto_tests/conference_peer_nick_test.c | 2 +- auto_tests/conference_test.c | 237 ++++---- auto_tests/friend_connection_test.c | 2 +- auto_tests/lossless_packet_test.c | 2 +- auto_tests/lossy_packet_test.c | 2 +- auto_tests/overflow_recvq_test.c | 2 +- auto_tests/overflow_sendq_test.c | 2 +- auto_tests/reconnect_test.c | 2 +- auto_tests/run_auto_test.h | 27 +- auto_tests/send_message_test.c | 2 +- docs/minpgc.md | 128 +++++ toxcore/group.c | 613 ++++++++++++++++----- toxcore/group.h | 26 +- toxcore/net_crypto.h | 1 + 15 files changed, 773 insertions(+), 277 deletions(-) create mode 100644 docs/minpgc.md diff --git a/auto_tests/conference_double_invite_test.c b/auto_tests/conference_double_invite_test.c index 36e93bc0..ba173a90 100644 --- a/auto_tests/conference_double_invite_test.c +++ b/auto_tests/conference_double_invite_test.c @@ -81,6 +81,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(2, conference_double_invite_test); + run_auto_test(2, conference_double_invite_test, false); return 0; } diff --git a/auto_tests/conference_peer_nick_test.c b/auto_tests/conference_peer_nick_test.c index eb9bee71..d8d2d64b 100644 --- a/auto_tests/conference_peer_nick_test.c +++ b/auto_tests/conference_peer_nick_test.c @@ -129,6 +129,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(2, conference_peer_nick_test); + run_auto_test(2, conference_peer_nick_test, false); return 0; } diff --git a/auto_tests/conference_test.c b/auto_tests/conference_test.c index 235200cd..fb6e7ca8 100644 --- a/auto_tests/conference_test.c +++ b/auto_tests/conference_test.c @@ -16,26 +16,31 @@ #include "check_compat.h" #define NUM_GROUP_TOX 16 +#define NUM_DISCONNECT 8 #define GROUP_MESSAGE "Install Gentoo" -#define NAME_FORMAT_STR "Tox #%4u" #define NAMELEN 9 -#define NAME_FORMAT "%9s" +#define NAME_FORMAT_STR "Tox #%4u" +#define NEW_NAME_FORMAT_STR "New #%4u" typedef struct State { - uint32_t id; + uint32_t index; + uint64_t clock; + bool invited_next; } 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->id); + printf("tox #%u: is now connected\n", state->index); } else { - printf("tox #%u: is now disconnected\n", state->id); + printf("tox #%u: is now disconnected\n", state->index); } } @@ -45,9 +50,9 @@ static void handle_friend_connection_status( const State *state = (State *)user_data; if (connection_status != TOX_CONNECTION_NONE) { - printf("tox #%u: is now connected to friend %u\n", state->id, friendnumber); + 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->id, friendnumber); + printf("tox #%u: is now disconnected from friend %u\n", state->index, friendnumber); } } @@ -56,18 +61,18 @@ static void handle_conference_invite( const uint8_t *data, size_t length, void *user_data) { const State *state = (State *)user_data; - ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %d", state->id, type); + ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %d", state->index, type); TOX_ERR_CONFERENCE_JOIN err; uint32_t g_num = tox_conference_join(tox, friendnumber, data, length, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%u: error joining group: %d", state->id, err); - ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", state->id); + ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%u: error joining group: %d", state->index, err); + ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", state->index); // Try joining again. We should only be allowed to join once. tox_conference_join(tox, friendnumber, data, length, &err); ck_assert_msg(err != TOX_ERR_CONFERENCE_JOIN_OK, - "tox #%u: joining groupchat twice should be impossible.", state->id); + "tox #%u: joining groupchat twice should be impossible.", state->index); } static void handle_conference_connected( @@ -81,8 +86,8 @@ static void handle_conference_connected( 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->id, err); - printf("tox #%u: invited next friend\n", state->id); + 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; } @@ -97,8 +102,93 @@ static void handle_conference_message( } } +static bool toxes_are_disconnected_from_group(uint32_t tox_count, Tox **toxes, int disconnected_count, + bool *disconnected) +{ + 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) { + return false; + } + } + + return true; +} + +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 void run_conference_tests(Tox **toxes, State *state) { + /* disabling name propagation check for now, as it occasionally fails due + * to disconnections too short to trigger freezing */ + const bool check_name_propagation = false; + + printf("letting random toxes timeout\n"); + bool disconnected[NUM_GROUP_TOX] = {0}; + + ck_assert(NUM_DISCONNECT < NUM_GROUP_TOX); + + for (uint16_t i = 0; i < NUM_DISCONNECT; ++i) { + uint32_t disconnect = random_false_index(disconnected, NUM_GROUP_TOX); + disconnected[disconnect] = true; + printf("Disconnecting #%u\n", state[disconnect].index); + } + + do { + for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + if (!disconnected[i]) { + tox_iterate(toxes[i], &state[i]); + state[i].clock += 1000; + } + } + + c_sleep(20); + } while (!toxes_are_disconnected_from_group(NUM_GROUP_TOX, toxes, NUM_DISCONNECT, disconnected)); + + if (check_name_propagation) { + printf("changing names\n"); + + for (uint16_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); + } + } + + printf("reconnecting toxes\n"); + + do { + iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); + } while (!all_connected_to_group(NUM_GROUP_TOX, toxes)); + + printf("running conference tests\n"); + for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { tox_callback_conference_message(toxes[i], &handle_conference_message); } @@ -112,41 +202,33 @@ static void run_conference_tests(Tox **toxes, State *state) err == TOX_ERR_CONFERENCE_SEND_MESSAGE_OK, "failed to send group message"); num_recv = 0; - for (uint8_t j = 0; j < 20; ++j) { - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - tox_iterate(toxes[i], &state[i]); - } - - c_sleep(25); + for (uint8_t j = 0; j < NUM_GROUP_TOX * 2; ++j) { + iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); } - c_sleep(25); ck_assert_msg(num_recv == NUM_GROUP_TOX, "failed to recv group messages"); for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { for (uint16_t j = 0; j < NUM_GROUP_TOX; ++j) { const size_t len = tox_conference_peer_get_name_size(toxes[i], 0, j, nullptr); - ck_assert_msg(len == NAMELEN, "name of #%u according to #%u has incorrect length %u", state[j].id, state[i].id, + ck_assert_msg(len == NAMELEN, "name of #%u according to #%u has incorrect length %u", state[j].index, state[i].index, (unsigned int)len); - uint8_t name[NAMELEN]; - tox_conference_peer_get_name(toxes[i], 0, j, name, nullptr); - char expected_name[NAMELEN + 1]; - snprintf(expected_name, NAMELEN + 1, NAME_FORMAT_STR, state[j].id); - ck_assert_msg(memcmp(name, expected_name, NAMELEN) == 0, - "name of #%u according to #%u is \"" NAME_FORMAT "\"; expected \"%s\"", - state[j].id, state[i].id, name, expected_name); + + if (check_name_propagation) { + uint8_t name[NAMELEN]; + tox_conference_peer_get_name(toxes[i], 0, j, name, nullptr); + /* Note the toxes will have been reordered */ + ck_assert_msg(memcmp(name, "New", 3) == 0, + "name of #%u according to #%u not updated", state[j].index, state[i].index); + } } } for (uint16_t k = NUM_GROUP_TOX; k != 0 ; --k) { tox_conference_delete(toxes[k - 1], 0, nullptr); - for (uint8_t j = 0; j < 10; ++j) { - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - tox_iterate(toxes[i], &state[i]); - } - - c_sleep(50); + 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) { @@ -158,82 +240,23 @@ static void run_conference_tests(Tox **toxes, State *state) } } -static void test_many_group(void) +static void test_many_group(Tox **toxes, State *state) { const time_t test_start_time = time(nullptr); - Tox *toxes[NUM_GROUP_TOX]; - State state[NUM_GROUP_TOX]; - memset(state, 0, NUM_GROUP_TOX * sizeof(State)); - time_t cur_time = time(nullptr); - struct Tox_Options *opts = tox_options_new(nullptr); - tox_options_set_start_port(opts, 33445); - tox_options_set_end_port(opts, 34445); - - printf("creating %d toxes\n", NUM_GROUP_TOX); - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - TOX_ERR_NEW err; - state[i].id = i + 1; - toxes[i] = tox_new_log(opts, &err, &state[i]); - - ck_assert_msg(toxes[i] != nullptr, "failed to create tox instance %u: error %d", i, err); 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); char name[NAMELEN + 1]; - snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, state[i].id); + snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, state[i].index); tox_self_set_name(toxes[i], (const uint8_t *)name, NAMELEN, nullptr); - - if (i != 0) { - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(toxes[0], dht_key); - const uint16_t dht_port = tox_self_get_udp_port(toxes[0], nullptr); - - tox_bootstrap(toxes[i], "localhost", dht_port, dht_key, nullptr); - } } - tox_options_free(opts); - - printf("creating a chain of friends\n"); - - for (unsigned i = 1; i < NUM_GROUP_TOX; ++i) { - TOX_ERR_FRIEND_ADD err; - uint8_t key[TOX_PUBLIC_KEY_SIZE]; - - tox_self_get_public_key(toxes[i - 1], key); - tox_friend_add_norequest(toxes[i], key, &err); - ck_assert_msg(err == TOX_ERR_FRIEND_ADD_OK, "failed to add friend: error %d", err); - - tox_self_get_public_key(toxes[i], key); - tox_friend_add_norequest(toxes[i - 1], key, &err); - ck_assert_msg(err == TOX_ERR_FRIEND_ADD_OK, "failed to add friend: error %d", err); - } - - printf("waiting for everyone to come online\n"); - unsigned online_count = 0; - - do { - online_count = 0; - - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - tox_iterate(toxes[i], &state[i]); - online_count += tox_friend_get_connection_status(toxes[i], 0, nullptr) != TOX_CONNECTION_NONE; - } - - printf("currently %u toxes are online\n", online_count); - fflush(stdout); - - c_sleep(1000); - } while (online_count != NUM_GROUP_TOX); - - printf("friends connected, took %d seconds\n", (int)(time(nullptr) - cur_time)); - ck_assert_msg(tox_conference_new(toxes[0], nullptr) != UINT32_MAX, "failed to create group"); - printf("tox #%u: inviting its first friend\n", state[0].id); + 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; ck_assert_msg(tox_conference_set_title(toxes[0], 0, (const uint8_t *)"Gentoo", sizeof("Gentoo") - 1, nullptr) != 0, @@ -244,17 +267,16 @@ static void test_many_group(void) uint16_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) { - tox_iterate(toxes[i], &state[i]); invited_count += state[i].invited_next; } - - c_sleep(50); } while (invited_count != NUM_GROUP_TOX - 1); - cur_time = time(nullptr); + uint64_t pregroup_clock = state[0].clock; printf("waiting for all toxes to be in the group\n"); uint16_t fully_connected_count = 0; @@ -262,8 +284,9 @@ static void test_many_group(void) fully_connected_count = 0; printf("current peer counts: ["); + iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); + for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - tox_iterate(toxes[i], &state[i]); TOX_ERR_CONFERENCE_PEER_QUERY err; uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, &err); @@ -282,8 +305,6 @@ static void test_many_group(void) printf("]\n"); fflush(stdout); - - c_sleep(200); } while (fully_connected_count != NUM_GROUP_TOX); for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { @@ -300,16 +321,10 @@ static void test_many_group(void) ck_assert_msg(memcmp("Gentoo", title, ret) == 0, "Wrong title"); } - printf("group connected, took %d seconds\n", (int)(time(nullptr) - cur_time)); + printf("group connected, took %d seconds\n", (int)((state[0].clock - pregroup_clock) / 1000)); run_conference_tests(toxes, state); - printf("tearing down toxes\n"); - - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - tox_kill(toxes[i]); - } - printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); } @@ -317,6 +332,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - test_many_group(); + run_auto_test(NUM_GROUP_TOX, test_many_group, true); return 0; } diff --git a/auto_tests/friend_connection_test.c b/auto_tests/friend_connection_test.c index ab91e0f0..55a930e2 100644 --- a/auto_tests/friend_connection_test.c +++ b/auto_tests/friend_connection_test.c @@ -27,6 +27,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(2, friend_connection_test); + run_auto_test(2, friend_connection_test, false); return 0; } diff --git a/auto_tests/lossless_packet_test.c b/auto_tests/lossless_packet_test.c index 5b8ee947..1b6de806 100644 --- a/auto_tests/lossless_packet_test.c +++ b/auto_tests/lossless_packet_test.c @@ -61,6 +61,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(2, test_lossless_packet); + run_auto_test(2, test_lossless_packet, false); return 0; } diff --git a/auto_tests/lossy_packet_test.c b/auto_tests/lossy_packet_test.c index 10d4e505..5080b006 100644 --- a/auto_tests/lossy_packet_test.c +++ b/auto_tests/lossy_packet_test.c @@ -57,6 +57,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(2, test_lossy_packet); + run_auto_test(2, test_lossy_packet, false); return 0; } diff --git a/auto_tests/overflow_recvq_test.c b/auto_tests/overflow_recvq_test.c index f70618c4..3f7561e1 100644 --- a/auto_tests/overflow_recvq_test.c +++ b/auto_tests/overflow_recvq_test.c @@ -62,6 +62,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(3, net_crypto_overflow_test); + run_auto_test(3, net_crypto_overflow_test, false); return 0; } diff --git a/auto_tests/overflow_sendq_test.c b/auto_tests/overflow_sendq_test.c index ce132d63..e67da1cf 100644 --- a/auto_tests/overflow_sendq_test.c +++ b/auto_tests/overflow_sendq_test.c @@ -47,6 +47,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(2, net_crypto_overflow_test); + run_auto_test(2, net_crypto_overflow_test, false); return 0; } diff --git a/auto_tests/reconnect_test.c b/auto_tests/reconnect_test.c index e56d818c..9ccceb39 100644 --- a/auto_tests/reconnect_test.c +++ b/auto_tests/reconnect_test.c @@ -101,6 +101,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(TOX_COUNT, test_reconnect); + run_auto_test(TOX_COUNT, test_reconnect, false); return 0; } diff --git a/auto_tests/run_auto_test.h b/auto_tests/run_auto_test.h index bcbfed55..4f2dc278 100644 --- a/auto_tests/run_auto_test.h +++ b/auto_tests/run_auto_test.h @@ -48,7 +48,7 @@ static uint64_t get_state_clock_callback(Mono_Time *mono_time, void *user_data) return state->clock; } -static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *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); Tox **toxes = (Tox **)calloc(tox_count, sizeof(Tox *)); @@ -66,18 +66,33 @@ static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *stat mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &state[i]); } - printf("toxes all add each other as friends\n"); + if (chain) { + printf("each tox adds adjacent toxes as friends\n"); + + for (uint32_t i = 0; i < tox_count; i++) { + for (uint32_t j = i - 1; j != i + 3; j += 2) { + if (j >= tox_count) { + continue; + } - for (uint32_t i = 0; i < tox_count; i++) { - for (uint32_t j = 0; j < tox_count; j++) { - if (i != j) { uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; tox_self_get_public_key(toxes[j], public_key); tox_friend_add_norequest(toxes[i], public_key, nullptr); } } - } + } else { + printf("toxes all add each other as friends\n"); + for (uint32_t i = 0; i < tox_count; i++) { + for (uint32_t j = 0; j < tox_count; j++) { + if (i != j) { + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(toxes[j], public_key); + tox_friend_add_norequest(toxes[i], public_key, nullptr); + } + } + } + } printf("bootstrapping all toxes off toxes[0]\n"); uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; diff --git a/auto_tests/send_message_test.c b/auto_tests/send_message_test.c index 81969d59..5346dc9e 100644 --- a/auto_tests/send_message_test.c +++ b/auto_tests/send_message_test.c @@ -61,6 +61,6 @@ int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); - run_auto_test(2, send_message_test); + run_auto_test(2, send_message_test, false); return 0; } diff --git a/docs/minpgc.md b/docs/minpgc.md new file mode 100644 index 00000000..aa2ed1dc --- /dev/null +++ b/docs/minpgc.md @@ -0,0 +1,128 @@ +# Persistent conferences + +This document describes the "minpgc" simple persistent conferences +implementation of PR #1069. + +Many of the ideas derive from isotoxin's persistent conferences +implementation, PR #826. + +## Specification of changes from pre-existing conference specification +We add one new packet type: + +Rejoin Conference packet + +| Length | Contents | +|:-------|:--------------------------------| +| `1` | `uint8_t` (0x64) | +| `33` | Group chat identifier | + + +A peer times out from a group if it has been inactive for 60s. When a peer +times out, we flag it as _frozen_. Frozen peers are disregarded for all +purposes except those discussed below - in particular no packets are sent to +them except as described below, they are omitted from the peer lists sent to +the client or in a Peer Response packet, and they are not considered when +determining closest peers for establishing direct connections. + +A peer is considered to be active if we receive a group message or Rejoin +packet from it, or a New Peer message for it. + +If a frozen peer is seen to be active, we remove its 'frozen' flag and send a +Name group message. (We can hold off on sending this message until the next +tox\_iterate, and only send one message if many frozen peers become active at +once). + +If we receive a New Peer message for a peer, we update its DHT pubkey. + +If we receive a group message originating from an unknown peer, we drop the +message but send a Peer Query packet back to the peer who directly sent us the +message. (This is current behaviour; it's mentioned here because it's important +and not currently mentioned in the spec.) + +If we receive a Rejoin packet from a peer we update its DHT pubkey, add a +temporary groupchat connection for the peer, and, once the connection is +online, send out a New Peer message announcing the peer, and a Name message. + +Whenever we make a new friend connection, we check if the public key is that +of any frozen peer. If so, we send it a Rejoin packet, add a temporary +groupchat connection for it, and, once the connection is online, send the +peer a Peer Query packet. + +We do the same with a peer when we are setting it as frozen if we have a +friend connection to it. + +The temporary groupchat connections established in sending and handling Rejoin +packets are not immediately operational (because group numbers are not known); +rather, an Online packet is sent when we handle a Rejoin packet. + +When a connection is set as online as a result of an Online packet, we ping +the group. + +When processing the reply to a Peer Query, we update the DHT pubkey of an +existing peer if and only if it is frozen or has not had its DHT pubkey +updated since it last stopped being frozen. + +When we receive a Title Response packet, we set the title if it has never been +set or if at some point since it was last set, there were no unfrozen peers +(except us). + +## Discussion +### Overview +The intention is to recover seamlessly from splits in the group, the most +common form of which is a single peer temporarily losing all connectivity. + +To see how this works, first note that groups (even before the changes +discussed here) have the property that for a group to be connected in the +sense that any peer will receive the messages of any other peer and have them +in their peerlist, it is necessary and sufficient that there is a path of +direct group connections between any two peers. + +Now suppose the group is split into two connected components, with each member +of one component frozen according to the members of the other. Suppose there +are two peers, one in each component, which are using the above protocol, and +suppose they establish a friend connection. Then each will rejoin the other, +forming a direct group connection. Hence the whole group will become connected +(even if all other peers are using the unmodified protocol). + +The Peer Query packet sent on rejoining hastens this process. + +Peers who leave the group during a split will not be deleted by all peers +after the merge - but they will be set as frozen due to ping timeouts, which +is sufficient. + +### Titles +If we have a split into components each containing multiple peers, and the +title is changed in one component, then peers will continue to disagree on the +title after the split. Short of a complicated voting system, this seems the +only reasonable behaviour. + +### Implementation notes +Although I've described the logic in terms of an 'frozen' flag, it might +actually make more sense in the implementation to have a separate list for +frozen peers. + +## Saving +Saving could be implemented by simply saving all live groups with their group +numbers and full peer info for all peers. On reload, all peers would be set as +frozen. + +The client would need to support this by understanding that these groups exist +on start-up (e.g. starting windows for them), and by not automatically killing +groups on closing the client. + +## Limitations +If a peer disconnects from the group for a period short enough that group +timeouts do not occur, and a name change occurs during this period, then the +name change will never be propagated. + +One way to deal with this would be a general mechanism for storing and +requesting missed group messages. But this is considered out of scope of this +PR. + +If a peer changes its DHT pubkey, the change might not be properly propagated +under various circumstances - in particular, if connections do not go down +long enough for the peer to become frozen. + +One way to deal with this would be to add a group message announcing the +sending peer's current DHT pubkey, and treat it analogously to the Name +message. diff --git a/toxcore/group.c b/toxcore/group.c index 4c77c0d4..c019c0eb 100644 --- a/toxcore/group.c +++ b/toxcore/group.c @@ -60,10 +60,10 @@ typedef enum Invite_Id { #define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) typedef enum Peer_Id { - PEER_KILL_ID = 1, - PEER_QUERY_ID = 8, - PEER_RESPONSE_ID = 9, - PEER_TITLE_ID = 10, + PEER_INTRODUCED_ID = 1, + PEER_QUERY_ID = 8, + PEER_RESPONSE_ID = 9, + PEER_TITLE_ID = 10, } Peer_Id; #define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1) @@ -232,7 +232,7 @@ int32_t conference_by_id(const Group_Chats *g_c, const uint8_t *id) /* * check if peer with peer_number is in peer array. * - * return peer number if peer is in chat. + * return peer index if peer is in chat. * return -1 if peer is not in chat. * * TODO(irungentoo): make this more efficient. @@ -267,10 +267,6 @@ typedef enum Groupchat_Closest { GROUPCHAT_CLOSEST_REMOVED } Groupchat_Closest; -static int friend_in_close(Group_c *g, int friendcon_id); -static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t groupnumber, uint8_t closest, - uint8_t lock); - static int add_to_closest(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk) { Group_c *g = get_group_c(g_c, groupnumber); @@ -371,6 +367,11 @@ static unsigned int pk_in_closest_peers(Group_c *g, uint8_t *real_pk) return 0; } +static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t groupnumber, uint8_t reason, + uint8_t lock); + +static void remove_conn_reason(Group_Chats *g_c, uint32_t groupnumber, uint16_t i, uint8_t reason); + static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t type, uint8_t *id); @@ -397,7 +398,7 @@ static int connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *user continue; } - if (!g->close[i].closest) { + if (!(g->close[i].reasons & GROUPCHAT_CLOSE_REASON_CLOSEST)) { continue; } @@ -406,12 +407,7 @@ static int connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *user get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[i].number); if (!pk_in_closest_peers(g, real_pk)) { - g->close[i].closest = false; - - if (!g->close[i].introducer && !g->close[i].introduced) { - g->close[i].type = GROUPCHAT_CLOSE_NONE; - kill_friend_connection(g_c->fr_c, g->close[i].number); - } + remove_conn_reason(g_c, groupnumber, i, GROUPCHAT_CLOSE_REASON_CLOSEST); } } @@ -435,7 +431,7 @@ static int connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *user set_dht_temp_pk(g_c->fr_c, friendcon_id, g->closest_peers[i].temp_pk, userdata); } - add_conn_to_groupchat(g_c, friendcon_id, groupnumber, 1, lock); + add_conn_to_groupchat(g_c, friendcon_id, groupnumber, GROUPCHAT_CLOSE_REASON_CLOSEST, lock); if (friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); @@ -447,7 +443,96 @@ static int connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *user return 0; } -/* Add a peer to the group chat. +static int get_frozen_index(Group_c *g, uint16_t peer_number) +{ + for (uint32_t i = 0; i < g->numfrozen; ++i) { + if (g->frozen[i].peer_number == peer_number) { + return i; + } + } + + return -1; +} + +/* Update last_active timestamp on peer, and thaw the peer if it is frozen. + * + * return peer index if peer is in the conference. + * return -1 otherwise, and on error. + */ +static int note_peer_active(Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_number, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + const int peer_index = get_peer_index(g, peer_number); + + if (peer_index != -1) { + g->group[peer_index].last_active = mono_time_get(g_c->mono_time); + return peer_index; + } + + const int frozen_index = get_frozen_index(g, peer_number); + + if (frozen_index == -1) { + return -1; + } + + /* Now thaw the peer */ + + Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); + + if (temp == nullptr) { + return -1; + } + + g->group = temp; + g->group[g->numpeers] = g->frozen[frozen_index]; + g->group[g->numpeers].temp_pk_updated = false; + g->group[g->numpeers].last_active = mono_time_get(g_c->mono_time); + + add_to_closest(g_c, groupnumber, g->group[g->numpeers].real_pk, g->group[g->numpeers].temp_pk); + + ++g->numpeers; + + --g->numfrozen; + + if (g->numfrozen == 0) { + free(g->frozen); + g->frozen = nullptr; + } else { + if (g->numfrozen != (uint32_t)frozen_index) { + g->frozen[frozen_index] = g->frozen[g->numfrozen]; + } + + Group_Peer *frozen_temp = (Group_Peer *)realloc(g->frozen, sizeof(Group_Peer) * (g->numfrozen)); + + if (frozen_temp == nullptr) { + return -1; + } + + g->frozen = frozen_temp; + } + + if (g_c->peer_list_changed_callback) { + g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); + } + + if (g->peer_on_join) { + g->peer_on_join(g->object, groupnumber, g->numpeers - 1); + } + + g->need_send_name = true; + + return g->numpeers - 1; +} + +/* Add a peer to the group chat, or update an existing peer. + * + * fresh indicates whether we should consider this information on the peer to + * be current, and so should update temp_pk and consider the peer active. * * do_gc_callback indicates whether we want to trigger callbacks set by the client * via the public API. This should be set to false if this function is called @@ -457,7 +542,7 @@ static int connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *user * return -1 if error. */ static int addpeer(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk, - uint16_t peer_number, void *userdata, bool do_gc_callback) + uint16_t peer_number, void *userdata, bool fresh, bool do_gc_callback) { Group_c *g = get_group_c(g_c, groupnumber); @@ -465,23 +550,35 @@ static int addpeer(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_p return -1; } - // TODO(irungentoo): - int peer_index = peer_in_chat(g, real_pk); + const int peer_index = fresh ? + note_peer_active(g_c, groupnumber, peer_number, userdata) : + get_peer_index(g, peer_number); if (peer_index != -1) { - id_copy(g->group[peer_index].temp_pk, temp_pk); - - if (g->group[peer_index].peer_number != peer_number) { + if (!id_equal(g->group[peer_index].real_pk, real_pk)) { return -1; } + if (fresh || !g->group[peer_index].temp_pk_updated) { + id_copy(g->group[peer_index].temp_pk, temp_pk); + g->group[peer_index].temp_pk_updated = true; + } + return peer_index; } - peer_index = get_peer_index(g, peer_number); + if (!fresh) { + const int frozen_index = get_frozen_index(g, peer_number); - if (peer_index != -1) { - return -1; + if (frozen_index != -1) { + if (!id_equal(g->frozen[frozen_index].real_pk, real_pk)) { + return -1; + } + + id_copy(g->frozen[frozen_index].temp_pk, temp_pk); + + return -1; + } } Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); @@ -495,9 +592,10 @@ static int addpeer(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_p id_copy(g->group[g->numpeers].real_pk, real_pk); id_copy(g->group[g->numpeers].temp_pk, temp_pk); + g->group[g->numpeers].temp_pk_updated = true; g->group[g->numpeers].peer_number = peer_number; - g->group[g->numpeers].last_recv = mono_time_get(g_c->mono_time); + g->group[g->numpeers].last_active = mono_time_get(g_c->mono_time); ++g->numpeers; add_to_closest(g_c, groupnumber, real_pk, temp_pk); @@ -539,13 +637,24 @@ static int remove_close_conn(Group_Chats *g_c, uint32_t groupnumber, int friendc } +static void remove_from_closest(Group_c *g, int peer_index) +{ + for (uint32_t i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (g->closest_peers[i].entry && id_equal(g->closest_peers[i].real_pk, g->group[peer_index].real_pk)) { + g->closest_peers[i].entry = 0; + g->changed = GROUPCHAT_CLOSEST_REMOVED; + break; + } + } +} + /* * Delete a peer from the group chat. * * return 0 if success * return -1 if error. */ -static int delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata) +static int delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata, bool keep_connection) { Group_c *g = get_group_c(g_c, groupnumber); @@ -553,19 +662,11 @@ static int delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void return -1; } - uint32_t i; + remove_from_closest(g, peer_index); - for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { /* If peer is in closest_peers list, remove it. */ - if (g->closest_peers[i].entry && id_equal(g->closest_peers[i].real_pk, g->group[peer_index].real_pk)) { - g->closest_peers[i].entry = 0; - g->changed = GROUPCHAT_CLOSEST_REMOVED; - break; - } - } + const int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->group[peer_index].real_pk); - int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->group[peer_index].real_pk); - - if (friendcon_id != -1) { + if (friendcon_id != -1 && !keep_connection) { remove_close_conn(g_c, groupnumber, friendcon_id); } @@ -578,7 +679,7 @@ static int delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void g->group = nullptr; } else { if (g->numpeers != (uint32_t)peer_index) { - memcpy(&g->group[peer_index], &g->group[g->numpeers], sizeof(Group_Peer)); + g->group[peer_index] = g->group[g->numpeers]; } Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers)); @@ -601,6 +702,28 @@ static int delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void return 0; } +static int freeze_peer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + Group_Peer *temp = (Group_Peer *)realloc(g->frozen, sizeof(Group_Peer) * (g->numfrozen + 1)); + + if (temp == nullptr) { + return -1; + } + + g->frozen = temp; + g->frozen[g->numfrozen] = g->group[peer_index]; + ++g->numfrozen; + + return delpeer(g_c, groupnumber, peer_index, userdata, true); +} + + /* Set the nick for a peer. * * do_gc_callback indicates whether we want to trigger callbacks set by the client @@ -666,6 +789,8 @@ static int settitle(Group_Chats *g_c, uint32_t groupnumber, int peer_index, cons memcpy(g->title, title, title_len); g->title_len = title_len; + g->title_fresh = true; + if (g_c->title_callback) { g_c->title_callback(g_c->m, groupnumber, peer_index, title, title_len, userdata); } @@ -673,7 +798,29 @@ static int settitle(Group_Chats *g_c, uint32_t groupnumber, int peer_index, cons return 0; } -static void set_conns_type_close(Group_Chats *g_c, uint32_t groupnumber, int friendcon_id, uint8_t type) +/* Check if the group has no online connection, and freeze all peers if so */ +static void check_disconnected(Group_Chats *g_c, uint32_t groupnumber, void *userdata) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_ONLINE) { + return; + } + } + + for (uint32_t i = 0; i < g->numpeers; ++i) { + while (i < g->numpeers && !id_equal(g->group[i].real_pk, g->real_pk)) { + freeze_peer(g_c, groupnumber, i, userdata); + } + } +} + +static void set_conns_type_close(Group_Chats *g_c, uint32_t groupnumber, int friendcon_id, uint8_t type, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); @@ -696,14 +843,38 @@ static void set_conns_type_close(Group_Chats *g_c, uint32_t groupnumber, int fri send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); } else { g->close[i].type = type; + check_disconnected(g_c, groupnumber, userdata); } } } /* Set the type for all close connections with friendcon_id */ -static void set_conns_status_groups(Group_Chats *g_c, int friendcon_id, uint8_t type) +static void set_conns_status_groups(Group_Chats *g_c, int friendcon_id, uint8_t type, void *userdata) { for (uint16_t i = 0; i < g_c->num_chats; ++i) { - set_conns_type_close(g_c, i, friendcon_id, type); + set_conns_type_close(g_c, i, friendcon_id, type, userdata); + } +} + +static bool try_send_rejoin(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk); + +static void rejoin_frozen_friend(Group_Chats *g_c, int friendcon_id) +{ + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, friendcon_id); + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (!g) { + continue; + } + + for (uint32_t j = 0; j < g->numfrozen; ++j) { + if (id_equal(g->frozen[j].real_pk, real_pk)) { + try_send_rejoin(g_c, i, real_pk); + break; + } + } } } @@ -712,9 +883,10 @@ static int g_handle_status(void *object, int friendcon_id, uint8_t status, void Group_Chats *g_c = (Group_Chats *)object; if (status) { /* Went online */ - set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_ONLINE); + set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_ONLINE, userdata); + rejoin_frozen_friend(g_c, friendcon_id); } else { /* Went offline */ - set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_CONNECTION); + set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_CONNECTION, userdata); // TODO(irungentoo): remove timedout connections? } @@ -729,7 +901,7 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin * return close index on success * return -1 on failure. */ -static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t groupnumber, uint8_t closest, +static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t groupnumber, uint8_t reason, uint8_t lock) { Group_c *g = get_group_c(g_c, groupnumber); @@ -738,40 +910,81 @@ static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t gr return -1; } - uint16_t i, ind = MAX_GROUP_CONNECTIONS; + uint16_t empty = MAX_GROUP_CONNECTIONS; + uint16_t ind = MAX_GROUP_CONNECTIONS; - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { - ind = i; + empty = i; continue; } if (g->close[i].number == (uint32_t)friendcon_id) { - g->close[i].closest |= closest; - return i; /* Already in list. */ + ind = i; /* Already in list. */ + break; } } + if (ind == MAX_GROUP_CONNECTIONS && empty != MAX_GROUP_CONNECTIONS) { + if (lock) { + friend_connection_lock(g_c->fr_c, friendcon_id); + } + + g->close[empty].type = GROUPCHAT_CLOSE_CONNECTION; + g->close[empty].number = friendcon_id; + g->close[empty].reasons = 0; + // TODO(irungentoo): + friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &g_handle_status, &g_handle_packet, + &handle_lossy, g_c, friendcon_id); + ind = empty; + } + if (ind == MAX_GROUP_CONNECTIONS) { return -1; } - if (lock) { - friend_connection_lock(g_c->fr_c, friendcon_id); + if (!(g->close[ind].reasons & reason)) { + g->close[ind].reasons |= reason; + + if (reason == GROUPCHAT_CLOSE_REASON_INTRODUCER) { + ++g->num_introducer_connections; + } } - g->close[ind].type = GROUPCHAT_CLOSE_CONNECTION; - g->close[ind].number = friendcon_id; - g->close[ind].closest = closest; - g->close[ind].introducer = false; - g->close[ind].introduced = false; - // TODO(irungentoo): - friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &g_handle_status, &g_handle_packet, - &handle_lossy, g_c, friendcon_id); - return ind; } +static unsigned int send_peer_introduced(Group_Chats *g_c, int friendcon_id, uint16_t group_num); + +/* Removes reason for keeping connection. + * + * Kills connection if this was the last reason. + */ +static void remove_conn_reason(Group_Chats *g_c, uint32_t groupnumber, uint16_t i, uint8_t reason) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + if (!(g->close[i].reasons & reason)) { + return; + } + + g->close[i].reasons &= ~reason; + + if (reason == GROUPCHAT_CLOSE_REASON_INTRODUCER) { + --g->num_introducer_connections; + send_peer_introduced(g_c, g->close[i].number, g->close[i].group_number); + } + + if (g->close[i].reasons == 0) { + kill_friend_connection(g_c->fr_c, g->close[i].number); + g->close[i].type = GROUPCHAT_CLOSE_NONE; + } +} + /* Creates a new groupchat and puts it in the chats array. * * type is one of GROUPCHAT_TYPE_* @@ -781,7 +994,7 @@ static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t gr */ int add_groupchat(Group_Chats *g_c, uint8_t type) { - int32_t groupnumber = create_group_chat(g_c); + const int32_t groupnumber = create_group_chat(g_c); if (groupnumber == -1) { return -1; @@ -790,12 +1003,12 @@ int add_groupchat(Group_Chats *g_c, uint8_t type) Group_c *g = &g_c->chats[groupnumber]; g->status = GROUPCHAT_STATUS_CONNECTED; - g->number_joined = -1; g->type = type; new_symmetric_key(g->id); g->peer_number = 0; /* Founder is peer 0. */ memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); - int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), 0, nullptr, false); + const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), 0, nullptr, true, + false); if (peer_index == -1) { return -1; @@ -838,6 +1051,7 @@ int del_groupchat(Group_Chats *g_c, uint32_t groupnumber) } free(g->group); + free(g->frozen); if (g->group_on_delete) { g->group_on_delete(g->object, groupnumber); @@ -1087,7 +1301,7 @@ int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) uint8_t invite[INVITE_PACKET_SIZE]; invite[0] = INVITE_ID; - uint16_t groupchat_num = net_htons((uint16_t)groupnumber); + const uint16_t groupchat_num = net_htons((uint16_t)groupnumber); memcpy(invite + 1, &groupchat_num, sizeof(groupchat_num)); invite[1 + sizeof(groupchat_num)] = g->type; memcpy(invite + 1 + sizeof(groupchat_num) + 1, g->id, GROUP_ID_LENGTH); @@ -1099,6 +1313,39 @@ int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) return -2; } +/* Send a rejoin packet to a peer if we have a friend connection to the peer. + * return true if a packet was sent. + * return false otherwise. + */ +static bool try_send_rejoin(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return false; + } + + const int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, real_pk); + + if (friendcon_id == -1) { + return false; + } + + uint8_t packet[1 + 1 + GROUP_ID_LENGTH]; + packet[0] = PACKET_ID_REJOIN_CONFERENCE; + packet[1] = g->type; + memcpy(packet + 2, g->id, GROUP_ID_LENGTH); + + if (write_cryptpacket(friendconn_net_crypto(g_c->fr_c), friend_connection_crypt_connection_id(g_c->fr_c, friendcon_id), + packet, sizeof(packet), 0) == -1) { + return false; + } + + add_conn_to_groupchat(g_c, friendcon_id, groupnumber, GROUPCHAT_CLOSE_REASON_INTRODUCER, 1); + + return true; +} + static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num); /* Join a group (you need to have been invited first.) @@ -1123,7 +1370,7 @@ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_typ return -2; } - int friendcon_id = getfriendcon_id(g_c->m, friendnumber); + const int friendcon_id = getfriendcon_id(g_c->m, friendnumber); if (friendcon_id == -1) { return -3; @@ -1133,7 +1380,7 @@ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_typ return -4; } - int groupnumber = create_group_chat(g_c); + const int groupnumber = create_group_chat(g_c); if (groupnumber == -1) { return -5; @@ -1141,9 +1388,8 @@ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_typ Group_c *g = &g_c->chats[groupnumber]; - uint16_t group_num = net_htons(groupnumber); + const uint16_t group_num = net_htons(groupnumber); g->status = GROUPCHAT_STATUS_VALID; - g->number_joined = -1; 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]; @@ -1157,13 +1403,11 @@ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_typ other_groupnum = net_ntohs(other_groupnum); g->type = data[sizeof(uint16_t)]; memcpy(g->id, data + sizeof(uint16_t) + 1, GROUP_ID_LENGTH); - int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnumber, 0, 1); + const int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnumber, GROUPCHAT_CLOSE_REASON_INTRODUCER, 1); if (close_index != -1) { g->close[close_index].group_number = other_groupnum; g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; - g->number_joined = friendcon_id; - g->close[close_index].introducer = true; } send_peer_query(g_c, friendcon_id, other_groupnum); @@ -1420,6 +1664,25 @@ int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title return g->title_len; } +static bool get_peer_number(const Group_c *g, const uint8_t *real_pk, uint16_t *peer_number) +{ + const int peer_index = peer_in_chat(g, real_pk); + + if (peer_index >= 0) { + *peer_number = g->group[peer_index].peer_number; + return true; + } + + for (uint32_t i = 0; i < g->numfrozen; ++i) { + if (id_equal(g->frozen[i].real_pk, real_pk)) { + *peer_number = g->frozen[i].peer_number; + return true; + } + } + + return false; +} + static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *userdata) { @@ -1430,7 +1693,7 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con } const uint8_t *invite_data = data + 1; - uint16_t invite_length = length - 1; + const uint16_t invite_length = length - 1; switch (data[0]) { case INVITE_ID: { @@ -1438,7 +1701,7 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con return; } - int groupnumber = get_group_num(g_c, data[1 + sizeof(uint16_t)], data + 1 + sizeof(uint16_t) + 1); + const int groupnumber = get_group_num(g_c, data[1 + sizeof(uint16_t)], data + 1 + sizeof(uint16_t) + 1); if (groupnumber == -1) { if (g_c->invite_callback) { @@ -1480,7 +1743,7 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con unsigned int tries = 0; - while (get_peer_index(g, peer_number) != -1) { + while (get_peer_index(g, peer_number) != -1 || get_frozen_index(g, peer_number) != -1) { peer_number = random_u16(); ++tries; @@ -1502,19 +1765,19 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE], temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); - addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true); - int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnum, 0, 1); + addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true, true); + const int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnum, GROUPCHAT_CLOSE_REASON_INTRODUCING, 1); if (close_index != -1) { g->close[close_index].group_number = other_groupnum; g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; - g->close[close_index].introduced = true; } group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); break; } + default: return; } @@ -1572,7 +1835,7 @@ static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16 sizeof(packet), 0) != -1; } -static unsigned int send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t group_num); +static int ping_groupchat(Group_Chats *g_c, uint32_t groupnumber); static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length) { @@ -1580,7 +1843,7 @@ static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_ return -1; } - int groupnumber = get_group_num(g_c, data[sizeof(uint16_t)], data + sizeof(uint16_t) + 1); + const int groupnumber = get_group_num(g_c, data[sizeof(uint16_t)], data + sizeof(uint16_t) + 1); if (groupnumber == -1) { return -1; @@ -1596,7 +1859,7 @@ static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_ return -1; } - int index = friend_in_close(g, friendcon_id); + const int index = friend_in_close(g, friendcon_id); if (index == -1) { return -1; @@ -1606,7 +1869,7 @@ static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_ return -1; } - if (count_close_connected(g) == 0) { + if (count_close_connected(g) == 0 || (g->close[index].reasons & GROUPCHAT_CLOSE_REASON_INTRODUCER)) { send_peer_query(g_c, friendcon_id, other_groupnum); } @@ -1614,18 +1877,68 @@ static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_ g->close[index].type = GROUPCHAT_CLOSE_ONLINE; send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); + if (g->close[index].reasons & GROUPCHAT_CLOSE_REASON_INTRODUCING) { + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE], temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); + + const int peer_index = peer_in_chat(g, real_pk); + + if (peer_index != -1) { + group_new_peer_send(g_c, groupnumber, g->group[peer_index].peer_number, real_pk, temp_pk); + } + + g->need_send_name = true; + } + + ping_groupchat(g_c, groupnumber); + return 0; } +static int handle_packet_rejoin(Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length, + void *userdata) +{ + if (length < 1 + GROUP_ID_LENGTH) { + return -1; + } + + const int32_t groupnum = get_group_num(g_c, *data, data + 1); + + Group_c *g = get_group_c(g_c, groupnum); + + if (!g) { + return -1; + } + + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE], temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); + + uint16_t peer_number; + + if (!get_peer_number(g, real_pk, &peer_number)) { + return -1; + } + + addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true, true); + const int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnum, GROUPCHAT_CLOSE_REASON_INTRODUCING, 1); + + if (close_index != -1) { + send_packet_online(g_c->fr_c, friendcon_id, groupnum, g->type, g->id); + } + + return 0; +} + + // we could send title with invite, but then if it changes between sending and accepting inv, joinee won't see it /* return 1 on success. * return 0 on failure */ -static unsigned int send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t group_num) +static unsigned int send_peer_introduced(Group_Chats *g_c, int friendcon_id, uint16_t group_num) { uint8_t packet[1]; - packet[0] = PEER_KILL_ID; + packet[0] = PEER_INTRODUCED_ID; return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); } @@ -1671,7 +1984,7 @@ static unsigned int send_peers(Group_Chats *g_c, uint32_t groupnumber, int frien p = response_packet + 1; } - uint16_t peer_num = net_htons(g->group[i].peer_number); + const uint16_t peer_num = net_htons(g->group[i].peer_number); memcpy(p, &peer_num, sizeof(peer_num)); p += sizeof(peer_num); memcpy(p, g->group[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE); @@ -1732,17 +2045,17 @@ static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8 g_c->connected_callback(g_c->m, groupnumber, userdata); } - group_name_send(g_c, groupnumber, g_c->m->name, g_c->m->name_length); + g->need_send_name = true; } - int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, true); + const int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, false, true); if (peer_index == -1) { return -1; } d += CRYPTO_PUBLIC_KEY_SIZE * 2; - uint8_t name_length = *d; + const uint8_t name_length = *d; d += 1; if (name_length > (length - (d - data)) || name_length > MAX_NAME_LENGTH) { @@ -1767,17 +2080,14 @@ static void handle_direct_packet(Group_Chats *g_c, uint32_t groupnumber, const u } switch (data[0]) { - case PEER_KILL_ID: { + case PEER_INTRODUCED_ID: { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return; } - if (!g->close[close_index].closest) { - g->close[close_index].type = GROUPCHAT_CLOSE_NONE; - kill_friend_connection(g_c->fr_c, g->close[close_index].number); - } + remove_conn_reason(g_c, groupnumber, close_index, GROUPCHAT_CLOSE_REASON_INTRODUCING); } break; @@ -1801,7 +2111,15 @@ static void handle_direct_packet(Group_Chats *g_c, uint32_t groupnumber, const u break; case PEER_TITLE_ID: { - settitle(g_c, groupnumber, -1, data + 1, length - 1, userdata); + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + break; + } + + if (!g->title_fresh) { + settitle(g_c, groupnumber, -1, data + 1, length - 1, userdata); + } } break; @@ -1868,7 +2186,7 @@ static unsigned int send_lossy_all_close(const Group_Chats *g_c, uint32_t groupn continue; } - if (g->close[i].closest) { + if (g->close[i].reasons & GROUPCHAT_CLOSE_REASON_CLOSEST) { connected_closest[num_connected_closest] = i; ++num_connected_closest; continue; @@ -1891,7 +2209,7 @@ static unsigned int send_lossy_all_close(const Group_Chats *g_c, uint32_t groupn uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE] = {0}; uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE] = {0}; get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[connected_closest[i]].number); - uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); + const uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); if (comp_val < comp_val_old) { to_send = connected_closest[i]; @@ -1911,7 +2229,7 @@ static unsigned int send_lossy_all_close(const Group_Chats *g_c, uint32_t groupn uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE] = {0}; uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE] = {0}; get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[connected_closest[i]].number); - uint64_t comp_val = calculate_comp_value(real_pk, g->real_pk); + const uint64_t comp_val = calculate_comp_value(real_pk, g->real_pk); if (comp_val < comp_val_old) { to_send_other = connected_closest[i]; @@ -1957,7 +2275,7 @@ static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint } VLA(uint8_t, packet, sizeof(uint16_t) + sizeof(uint32_t) + 1 + len); - uint16_t peer_num = net_htons(g->peer_number); + const uint16_t peer_num = net_htons(g->peer_number); memcpy(packet, &peer_num, sizeof(peer_num)); ++g->message_number; @@ -1966,7 +2284,7 @@ static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint ++g->message_number; } - uint32_t message_num = net_htonl(g->message_number); + const uint32_t message_num = net_htonl(g->message_number); memcpy(packet + sizeof(uint16_t), &message_num, sizeof(message_num)); packet[sizeof(uint16_t) + sizeof(uint32_t)] = message_id; @@ -1986,7 +2304,7 @@ static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint */ int group_message_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *message, uint16_t length) { - int ret = send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length); + const int ret = send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length); if (ret > 0) { return 0; @@ -2001,7 +2319,7 @@ int group_message_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8 */ int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *action, uint16_t length) { - int ret = send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length); + const int ret = send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length); if (ret > 0) { return 0; @@ -2025,9 +2343,9 @@ int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const } VLA(uint8_t, packet, sizeof(uint16_t) * 2 + length); - uint16_t peer_number = net_htons(g->peer_number); + const uint16_t peer_number = net_htons(g->peer_number); memcpy(packet, &peer_number, sizeof(uint16_t)); - uint16_t message_num = net_htons(g->lossy_message_number); + const uint16_t message_num = net_htons(g->lossy_message_number); memcpy(packet + sizeof(uint16_t), &message_num, sizeof(uint16_t)); memcpy(packet + sizeof(uint16_t) * 2, data, length); @@ -2108,33 +2426,31 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, memcpy(&peer_number, data, sizeof(uint16_t)); peer_number = net_ntohs(peer_number); - int index = get_peer_index(g, peer_number); + const int index = note_peer_active(g_c, groupnumber, peer_number, userdata); if (index == -1) { - /* We don't know the peer this packet came from so we query the list of peers from that peer. - (They would not have relayed it if they didn't know the peer.) */ + /* If we don't know the peer this packet came from, then we query the + * list of peers from the relaying peer. + * (They would not have relayed it if they didn't know the peer.) */ send_peer_query(g_c, g->close[close_index].number, g->close[close_index].group_number); return; } - if (g->number_joined != -1 && count_close_connected(g) >= DESIRED_CLOSE_CONNECTIONS) { - const int fr_close_index = friend_in_close(g, g->number_joined); + if (g->num_introducer_connections > 0 && count_close_connected(g) > DESIRED_CLOSE_CONNECTIONS) { + for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { + if (g->close[i].type == GROUPCHAT_CLOSE_NONE + || !(g->close[i].reasons & GROUPCHAT_CLOSE_REASON_INTRODUCER) + || i == close_index) { + continue; + } - if (fr_close_index >= 0 && fr_close_index != close_index && !g->close[fr_close_index].closest) { uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; - get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->close[fr_close_index].number); + get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->close[i].number); if (id_equal(g->group[index].real_pk, real_pk)) { /* Received message from peer relayed via another peer, so * the introduction was successful */ - g->number_joined = -1; - g->close[fr_close_index].introducer = false; - - if (!g->close[fr_close_index].closest && !g->close[fr_close_index].introduced) { - g->close[fr_close_index].type = GROUPCHAT_CLOSE_NONE; - send_peer_kill(g_c, g->close[fr_close_index].number, g->close[fr_close_index].group_number); - kill_friend_connection(g_c->fr_c, g->close[fr_close_index].number); - } + remove_conn_reason(g_c, groupnumber, i, GROUPCHAT_CLOSE_REASON_INTRODUCER); } } } @@ -2143,24 +2459,17 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); message_number = net_ntohl(message_number); - uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)]; + const uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)]; const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(message_number) + 1; - uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1); + const uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1); - // FIXME(zugz) update discussion of message numbers in the spec if (!check_message_info(message_number, message_id, &g->group[index])) { return; } switch (message_id) { - case GROUP_MESSAGE_PING_ID: { - if (msg_data_len != 0) { - return; - } - - g->group[index].last_recv = mono_time_get(g_c->mono_time); - } - break; + case GROUP_MESSAGE_PING_ID: + break; case GROUP_MESSAGE_NEW_PEER_ID: { if (msg_data_len != GROUP_MESSAGE_NEW_PEER_LENGTH) { @@ -2171,7 +2480,7 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, memcpy(&new_peer_number, msg_data, sizeof(uint16_t)); new_peer_number = net_ntohs(new_peer_number); addpeer(g_c, groupnumber, msg_data + sizeof(uint16_t), msg_data + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE, - new_peer_number, userdata, true); + new_peer_number, userdata, true, true); } break; @@ -2185,7 +2494,7 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, kill_peer_number = net_ntohs(kill_peer_number); if (peer_number == kill_peer_number) { - delpeer(g_c, groupnumber, index, userdata); + delpeer(g_c, groupnumber, index, userdata, false); } else { return; // TODO(irungentoo): @@ -2260,6 +2569,10 @@ static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, return handle_packet_online(g_c, friendcon_id, data + 1, length - 1); } + if (data[0] == PACKET_ID_REJOIN_CONFERENCE) { + return handle_packet_rejoin(g_c, friendcon_id, data + 1, length - 1, userdata); + } + if (data[0] != PACKET_ID_DIRECT_CONFERENCE && data[0] != PACKET_ID_MESSAGE_CONFERENCE) { return -1; } @@ -2273,7 +2586,7 @@ static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, return -1; } - int index = friend_in_close(g, friendcon_id); + const int index = friend_in_close(g, friendcon_id); if (index == -1) { return -1; @@ -2335,7 +2648,7 @@ static unsigned int lossy_packet_not_received(Group_c *g, int peer_index, uint16 return -1; } - uint16_t top_distance = message_number - g->group[peer_index].top_lossy_number; + const uint16_t top_distance = message_number - g->group[peer_index].top_lossy_number; if (top_distance >= MAX_LOSSY_COUNT) { crypto_memzero(g->group[peer_index].recv_lossy, sizeof(g->group[peer_index].recv_lossy)); @@ -2384,7 +2697,7 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin return -1; } - int index = friend_in_close(g, friendcon_id); + const int index = friend_in_close(g, friendcon_id); if (index == -1) { return -1; @@ -2394,7 +2707,7 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin return -1; } - int peer_index = get_peer_index(g, peer_number); + const int peer_index = get_peer_index(g, peer_number); if (peer_index == -1) { return -1; @@ -2406,7 +2719,7 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin const uint8_t *lossy_data = data + 1 + sizeof(uint16_t) * 3; uint16_t lossy_length = length - (1 + sizeof(uint16_t) * 3); - uint8_t message_id = lossy_data[0]; + const uint8_t message_id = lossy_data[0]; ++lossy_data; --lossy_length; @@ -2517,7 +2830,7 @@ static int ping_groupchat(Group_Chats *g_c, uint32_t groupnumber) return 0; } -static int groupchat_clear_timedout(Group_Chats *g_c, uint32_t groupnumber, void *userdata) +static int groupchat_freeze_timedout(Group_Chats *g_c, uint32_t groupnumber, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); @@ -2526,16 +2839,20 @@ static int groupchat_clear_timedout(Group_Chats *g_c, uint32_t groupnumber, void } for (uint32_t i = 0; i < g->numpeers; ++i) { - if (g->peer_number != g->group[i].peer_number - && mono_time_is_timeout(g_c->mono_time, g->group[i].last_recv, GROUP_PING_INTERVAL * 3)) { - delpeer(g_c, groupnumber, i, userdata); + if (g->group[i].peer_number == g->peer_number) { + continue; } - if (g->group == nullptr || i >= g->numpeers) { - break; + if (mono_time_is_timeout(g_c->mono_time, g->group[i].last_active, GROUP_PING_INTERVAL * 3)) { + try_send_rejoin(g_c, groupnumber, g->group[i].real_pk); + freeze_peer(g_c, groupnumber, i, userdata); } } + if (g->numpeers <= 1) { + g->title_fresh = false; + } + return 0; } @@ -2552,6 +2869,7 @@ void send_name_all_groups(Group_Chats *g_c) if (g->status == GROUPCHAT_STATUS_CONNECTED) { group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); + g->need_send_name = false; } } } @@ -2591,7 +2909,12 @@ void do_groupchats(Group_Chats *g_c, void *userdata) if (g->status == GROUPCHAT_STATUS_CONNECTED) { connect_to_closest(g_c, i, userdata); ping_groupchat(g_c, i); - groupchat_clear_timedout(g_c, i, userdata); + groupchat_freeze_timedout(g_c, i, userdata); + + if (g->need_send_name) { + group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); + g->need_send_name = false; + } } } diff --git a/toxcore/group.h b/toxcore/group.h index 68488e27..110f5d44 100644 --- a/toxcore/group.h +++ b/toxcore/group.h @@ -49,12 +49,13 @@ typedef struct Message_Info { typedef struct Group_Peer { uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + bool temp_pk_updated; - uint64_t last_recv; + uint64_t last_active; Message_Info last_message_infos[MAX_LAST_MESSAGE_INFOS]; /* received messages, strictly decreasing in message_number */ - uint8_t num_last_message_infos; + uint8_t num_last_message_infos; uint8_t nick[MAX_NAME_LENGTH]; uint8_t nick_len; @@ -79,11 +80,18 @@ typedef enum Groupchat_Close_Type { GROUPCHAT_CLOSE_ONLINE } Groupchat_Close_Type; +/* Connection is to one of the closest DESIRED_CLOSE_CONNECTIONS peers */ +#define GROUPCHAT_CLOSE_REASON_CLOSEST (1 << 0) + +/* Connection is to a peer we are introducing to the conference */ +#define GROUPCHAT_CLOSE_REASON_INTRODUCING (1 << 1) + +/* Connection is to a peer who is introducing us to the conference */ +#define GROUPCHAT_CLOSE_REASON_INTRODUCER (1 << 2) + typedef struct Groupchat_Close { uint8_t type; /* GROUPCHAT_CLOSE_* */ - bool closest; /* connected to peer because it is one of our closest peers */ - bool introducer; /* connected to peer because it introduced us to the group */ - bool introduced; /* connected to peer because we introduced it to the group */ + uint8_t reasons; /* bit field with flags GROUPCHAT_CLOSE_REASON_* */ uint32_t number; uint16_t group_number; } Groupchat_Close; @@ -101,9 +109,15 @@ typedef void group_on_delete_cb(void *object, uint32_t conference_number); typedef struct Group_c { uint8_t status; + bool need_send_name; + bool title_fresh; + Group_Peer *group; uint32_t numpeers; + Group_Peer *frozen; + uint32_t numfrozen; + /* TODO(zugz) rename close to something more accurate - "connected"? */ Groupchat_Close close[MAX_GROUP_CONNECTIONS]; @@ -123,7 +137,7 @@ typedef struct Group_c { uint64_t last_sent_ping; - int number_joined; /* friendcon_id of person that invited us to the chat. (-1 means none) */ + uint32_t num_introducer_connections; void *object; diff --git a/toxcore/net_crypto.h b/toxcore/net_crypto.h index 7a2ff1ff..9fd47014 100644 --- a/toxcore/net_crypto.h +++ b/toxcore/net_crypto.h @@ -78,6 +78,7 @@ #define PACKET_ID_ONLINE_PACKET 97 #define PACKET_ID_DIRECT_CONFERENCE 98 #define PACKET_ID_MESSAGE_CONFERENCE 99 +#define PACKET_ID_REJOIN_CONFERENCE 100 #define PACKET_ID_LOSSY_CONFERENCE 199 /*** Crypto connections. ***/