mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
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
This commit is contained in:
parent
6872c14e1a
commit
1b2322284f
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
for (uint8_t j = 0; j < NUM_GROUP_TOX * 2; ++j) {
|
||||
iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
|
||||
}
|
||||
|
||||
c_sleep(25);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (check_name_propagation) {
|
||||
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);
|
||||
/* 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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,6 +66,21 @@ 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]);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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++) {
|
||||
|
@ -77,7 +92,7 @@ static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *stat
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
printf("bootstrapping all toxes off toxes[0]\n");
|
||||
uint8_t dht_key[TOX_PUBLIC_KEY_SIZE];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
128
docs/minpgc.md
Normal file
128
docs/minpgc.md
Normal file
|
@ -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.
|
601
toxcore/group.c
601
toxcore/group.c
File diff suppressed because it is too large
Load Diff
|
@ -49,8 +49,9 @@ 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 */
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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. ***/
|
||||
|
|
Loading…
Reference in New Issue
Block a user