From 9770880e975a09635a461c46c8fcc193bce57004 Mon Sep 17 00:00:00 2001 From: "zugz (tox)" Date: Sun, 9 Sep 2018 23:27:39 +0200 Subject: Implement conferences saving * add global friend_connection status callback, used for group rejoining * stop leaving groups on killing tox --- auto_tests/conference_test.c | 34 +++++- docs/minpgc.md | 14 +-- toxcore/Messenger.h | 3 + toxcore/friend_connection.c | 16 ++- toxcore/friend_connection.h | 5 + toxcore/group.c | 259 ++++++++++++++++++++++++++++++++++++++++++- toxcore/group.h | 11 +- toxcore/state.h | 1 + toxcore/tox.c | 1 + 9 files changed, 327 insertions(+), 17 deletions(-) diff --git a/auto_tests/conference_test.c b/auto_tests/conference_test.c index 647b649d..349c2905 100644 --- a/auto_tests/conference_test.c +++ b/auto_tests/conference_test.c @@ -167,13 +167,34 @@ static void run_conference_tests(Tox **toxes, State *state) printf("letting random toxes timeout\n"); bool disconnected[NUM_GROUP_TOX] = {0}; + bool restarting[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); + + if (i < NUM_DISCONNECT / 2) { + restarting[disconnect] = true; + printf("Restarting #%u\n", state[disconnect].index); + } else { + printf("Disconnecting #%u\n", state[disconnect].index); + } + } + + uint8_t *save[NUM_GROUP_TOX]; + size_t save_size[NUM_GROUP_TOX]; + + for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + if (restarting[i]) { + save_size[i] = tox_get_savedata_size(toxes[i]); + ck_assert_msg(save_size[i] != 0, "save is invalid size %u", (unsigned)save_size[i]); + save[i] = (uint8_t *)malloc(save_size[i]); + ck_assert_msg(save[i] != nullptr, "malloc failed"); + tox_get_savedata(toxes[i], save[i]); + tox_kill(toxes[i]); + } } do { @@ -187,6 +208,17 @@ static void run_conference_tests(Tox **toxes, State *state) c_sleep(20); } while (!toxes_are_disconnected_from_group(NUM_GROUP_TOX, toxes, NUM_DISCONNECT, disconnected)); + for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + if (restarting[i]) { + struct Tox_Options *const options = tox_options_new(nullptr); + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + tox_options_set_savedata_data(options, save[i], save_size[i]); + toxes[i] = tox_new_log(options, nullptr, &state[i].index); + tox_options_free(options); + free(save[i]); + } + } + if (check_name_change_propagation) { printf("changing names\n"); diff --git a/docs/minpgc.md b/docs/minpgc.md index aa2ed1dc..a09895bd 100644 --- a/docs/minpgc.md +++ b/docs/minpgc.md @@ -29,7 +29,7 @@ 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 +`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. @@ -102,13 +102,13 @@ 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. +Saving is implemented by simply saving all live groups with their group numbers +and full peer info for all peers. On reload, all peers are 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. +Clients needs to support this by understanding that groups may exist on +start-up. Clients should call `tox_conference_get_chatlist` to obtain them. A +group which is deleted (with `tox_conference_delete`) is removed permanently +and will not be saved. ## Limitations If a peer disconnects from the group for a period short enough that group diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h index 1651315e..a3376e23 100644 --- a/toxcore/Messenger.h +++ b/toxcore/Messenger.h @@ -796,6 +796,9 @@ uint8_t *messenger_save(const Messenger *m, uint8_t *data); /* Load a state section. * + * @param data Data to load. + * @param length Length of data. + * @param type Type of section (STATE_TYPE_*). * @param status Result of loading section is stored here if the section is handled. * @return true iff section handled. */ diff --git a/toxcore/friend_connection.c b/toxcore/friend_connection.c index 9c805b45..69533def 100644 --- a/toxcore/friend_connection.c +++ b/toxcore/friend_connection.c @@ -85,6 +85,9 @@ struct Friend_Connections { fr_request_cb *fr_request_callback; void *fr_request_object; + global_status_cb *global_status_callback; + void *global_status_callback_object; + uint64_t last_lan_discovery; uint16_t next_lan_port; @@ -401,9 +404,11 @@ static int handle_status(void *object, int number, uint8_t status, void *userdat } if (status_changed) { - unsigned int i; + if (fr_c->global_status_callback) { + fr_c->global_status_callback(fr_c->global_status_callback_object, number, status, userdata); + } - for (i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { + for (unsigned i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { if (friend_con->callbacks[i].status_callback) { friend_con->callbacks[i].status_callback( friend_con->callbacks[i].callback_object, @@ -716,6 +721,13 @@ int friend_connection_callbacks(Friend_Connections *fr_c, int friendcon_id, unsi return 0; } +/* Set global status callback for friend connections. */ +void set_global_status_callback(Friend_Connections *fr_c, global_status_cb *global_status_callback, void *object) +{ + fr_c->global_status_callback = global_status_callback; + fr_c->global_status_callback_object = object; +} + /* return the crypt_connection_id for the connection. * * return crypt_connection_id on success. diff --git a/toxcore/friend_connection.h b/toxcore/friend_connection.h index 149a4fa7..166c731b 100644 --- a/toxcore/friend_connection.h +++ b/toxcore/friend_connection.h @@ -101,10 +101,15 @@ void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t * */ int friend_add_tcp_relay(Friend_Connections *fr_c, int friendcon_id, IP_Port ip_port, const uint8_t *public_key); +typedef int global_status_cb(void *object, int id, uint8_t status, void *userdata); + typedef int fc_status_cb(void *object, int id, uint8_t status, void *userdata); typedef int fc_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); typedef int fc_lossy_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); +/* Set global status callback for friend connections. */ +void set_global_status_callback(Friend_Connections *fr_c, global_status_cb *global_status_callback, void *object); + /* Set the callbacks for the friend connection. * index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we want the callback to set in the array. * diff --git a/toxcore/group.c b/toxcore/group.c index 9ef32b72..99f72336 100644 --- a/toxcore/group.c +++ b/toxcore/group.c @@ -27,10 +27,12 @@ #include "group.h" +#include #include #include #include "mono_time.h" +#include "state.h" #include "util.h" /** @@ -880,13 +882,23 @@ static void rejoin_frozen_friend(Group_Chats *g_c, int friendcon_id) } } +static int g_handle_any_status(void *object, int friendcon_id, uint8_t status, void *userdata) +{ + Group_Chats *g_c = (Group_Chats *)object; + + if (status) { + rejoin_frozen_friend(g_c, friendcon_id); + } + + return 0; +} + static int g_handle_status(void *object, int friendcon_id, uint8_t status, void *userdata) { Group_Chats *g_c = (Group_Chats *)object; if (status) { /* Went 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, userdata); // TODO(irungentoo): remove timedout connections? @@ -1021,7 +1033,6 @@ int add_groupchat(Group_Chats *g_c, uint8_t type) return groupnumber; } -static int group_kill_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num); /* Delete a groupchat from the chats array. * * return 0 on success. @@ -1035,8 +1046,6 @@ int del_groupchat(Group_Chats *g_c, uint32_t groupnumber) return -1; } - group_kill_peer_send(g_c, groupnumber, g->peer_number); - for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { continue; @@ -1587,6 +1596,22 @@ static int group_name_send(const Group_Chats *g_c, uint32_t groupnumber, const u return -1; } +/* send message to announce leaving group + * return true on success + * return false on failure + */ +bool group_leave(const Group_Chats *g_c, uint32_t groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return false; + } + + return group_kill_peer_send(g_c, groupnumber, g->peer_number) == 0; +} + + /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success * return -1 if groupnumber is invalid. @@ -2875,22 +2900,242 @@ void send_name_all_groups(Group_Chats *g_c) } } +#define SAVED_PEER_SIZE_CONSTANT (2 * CRYPTO_PUBLIC_KEY_SIZE + 2 + 1) + +static uint32_t saved_peer_size(const Group_Peer *peer) +{ + return SAVED_PEER_SIZE_CONSTANT + peer->nick_len; +} + +static uint8_t *save_peer(const Group_Peer *peer, uint8_t *data) +{ + memcpy(data, peer->real_pk, CRYPTO_PUBLIC_KEY_SIZE); + data += CRYPTO_PUBLIC_KEY_SIZE; + + memcpy(data, peer->temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + data += CRYPTO_PUBLIC_KEY_SIZE; + + host_to_lendian_bytes16(data, peer->peer_number); + data += sizeof(uint16_t); + + *data = peer->nick_len; + ++data; + + memcpy(data, peer->nick, peer->nick_len); + data += peer->nick_len; + + return data; +} + +#define SAVED_CONF_SIZE_CONSTANT (1 + GROUP_ID_LENGTH + sizeof(uint32_t) \ + + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + 1) + +static uint32_t saved_conf_size(const Group_c *g) +{ + uint32_t len = SAVED_CONF_SIZE_CONSTANT + g->title_len; + + for (uint32_t j = 0; j < g->numpeers + g->numfrozen; ++j) { + const Group_Peer *peer = (j < g->numpeers) ? &g->group[j] : &g->frozen[j - g->numpeers]; + + if (id_equal(peer->real_pk, g->real_pk)) { + continue; + } + + len += saved_peer_size(peer); + } + + return len; +} + +static uint8_t *save_conf(const Group_c *g, uint8_t *data) +{ + *data = g->type; + ++data; + + memcpy(data, g->id, GROUP_ID_LENGTH); + data += GROUP_ID_LENGTH; + + host_to_lendian_bytes32(data, g->message_number); + data += sizeof(uint32_t); + + host_to_lendian_bytes16(data, g->lossy_message_number); + data += sizeof(uint16_t); + + host_to_lendian_bytes16(data, g->peer_number); + data += sizeof(uint16_t); + + host_to_lendian_bytes32(data, g->numpeers - 1 + g->numfrozen); + data += sizeof(uint32_t); + + *data = g->title_len; + ++data; + + memcpy(data, g->title, g->title_len); + data += g->title_len; + +#ifndef NDEBUG + bool found_self = false; +#endif + + for (uint32_t j = 0; j < g->numpeers + g->numfrozen; ++j) { + const Group_Peer *peer = (j < g->numpeers) ? &g->group[j] : &g->frozen[j - g->numpeers]; + + if (id_equal(peer->real_pk, g->real_pk)) { +#ifndef NDEBUG + found_self = true; +#endif + continue; + } + + data = save_peer(peer, data); + } + + assert(found_self); + + return data; +} + +static uint32_t conferences_section_size(const Group_Chats *g_c) +{ + uint32_t len = 0; + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (!g || g->status != GROUPCHAT_STATUS_CONNECTED) { + continue; + } + + len += saved_conf_size(g); + } + + return len; +} + uint32_t conferences_size(const Group_Chats *g_c) { - return 0; + return 2 * sizeof(uint32_t) + conferences_section_size(g_c); } uint8_t *conferences_save(const Group_Chats *g_c, uint8_t *data) { + const uint32_t len = conferences_section_size(g_c); + data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_CONFERENCES); + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (!g || g->status != GROUPCHAT_STATUS_CONNECTED) { + continue; + } + + data = save_conf(g, data); + } + return data; } +static State_Load_Status load_conferences(Group_Chats *g_c, const uint8_t *data, uint32_t length) +{ + const uint8_t *init_data = data; + + while (length >= (uint32_t)(data - init_data) + SAVED_CONF_SIZE_CONSTANT) { + const int groupnumber = create_group_chat(g_c); + + if (groupnumber == -1) { + return STATE_LOAD_STATUS_ERROR; + } + + Group_c *g = &g_c->chats[groupnumber]; + + g->type = *data; + ++data; + + memcpy(g->id, data, GROUP_ID_LENGTH); + data += GROUP_ID_LENGTH; + + lendian_bytes_to_host32(&g->message_number, data); + data += sizeof(uint32_t); + + lendian_bytes_to_host16(&g->lossy_message_number, data); + data += sizeof(uint16_t); + + lendian_bytes_to_host16(&g->peer_number, data); + data += sizeof(uint16_t); + + lendian_bytes_to_host32(&g->numfrozen, data); + data += sizeof(uint32_t); + + g->frozen = (Group_Peer *)malloc(sizeof(Group_Peer) * g->numfrozen); + + if (g->frozen == nullptr) { + return STATE_LOAD_STATUS_ERROR; + } + + g->title_len = *data; + ++data; + + if (length < (uint32_t)(data - init_data) + g->title_len) { + return STATE_LOAD_STATUS_ERROR; + } + + memcpy(g->title, data, g->title_len); + data += g->title_len; + + for (uint32_t j = 0; j < g->numfrozen; ++j) { + if (length < (uint32_t)(data - init_data) + SAVED_PEER_SIZE_CONSTANT) { + return STATE_LOAD_STATUS_ERROR; + } + + Group_Peer *peer = &g->frozen[j]; + memset(peer, 0, sizeof(Group_Peer)); + + id_copy(peer->real_pk, data); + data += CRYPTO_PUBLIC_KEY_SIZE; + id_copy(peer->temp_pk, data); + data += CRYPTO_PUBLIC_KEY_SIZE; + + lendian_bytes_to_host16(&peer->peer_number, data); + data += sizeof(uint16_t); + + peer->nick_len = *data; + ++data; + + if (length < (uint32_t)(data - init_data) + peer->nick_len) { + return STATE_LOAD_STATUS_ERROR; + } + + memcpy(peer->nick, data, peer->nick_len); + data += peer->nick_len; + } + + g->status = GROUPCHAT_STATUS_CONNECTED; + memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); + const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), g->peer_number, + nullptr, true, false); + + if (peer_index == -1) { + return STATE_LOAD_STATUS_ERROR; + } + + setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, nullptr, false); + } + + return STATE_LOAD_STATUS_CONTINUE; +} + bool conferences_load_state_section(Group_Chats *g_c, const uint8_t *data, uint32_t length, uint16_t type, State_Load_Status *status) { - return false; + if (type != STATE_TYPE_CONFERENCES) { + return false; + } + + *status = load_conferences(g_c, data, length); + return true; } + /* Create new groupchat instance. */ Group_Chats *new_groupchats(Mono_Time *mono_time, Messenger *m) { @@ -2910,6 +3155,8 @@ Group_Chats *new_groupchats(Mono_Time *mono_time, Messenger *m) m->conferences_object = temp; m_callback_conference_invite(m, &handle_friend_invite_packet); + set_global_status_callback(m->fr_c, &g_handle_any_status, temp); + return temp; } diff --git a/toxcore/group.h b/toxcore/group.h index 8673abdb..148de0c1 100644 --- a/toxcore/group.h +++ b/toxcore/group.h @@ -306,6 +306,12 @@ 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); +/* send message to announce leaving group + * return true on success + * return false on failure + */ +bool group_leave(const Group_Chats *g_c, uint32_t groupnumber); + /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success * return -1 if groupnumber is invalid. @@ -455,8 +461,11 @@ uint32_t conferences_size(const Group_Chats *g_c); uint8_t *conferences_save(const Group_Chats *g_c, uint8_t *data); /** - * Load a section. + * Load a state section. * + * @param data Data to load + * @param length Length of data + * @param type Type of section (STATE_TYPE_*) * @param status Result of loading section is stored here if the section is handled. * @return true iff section handled. */ diff --git a/toxcore/state.h b/toxcore/state.h index 872b1e9d..6e3c897e 100644 --- a/toxcore/state.h +++ b/toxcore/state.h @@ -31,6 +31,7 @@ typedef enum State_Type { STATE_TYPE_STATUS = 6, STATE_TYPE_TCP_RELAY = 10, STATE_TYPE_PATH_NODE = 11, + STATE_TYPE_CONFERENCES = 20, STATE_TYPE_END = 255, } State_Type; diff --git a/toxcore/tox.c b/toxcore/tox.c index 629cc1c5..88a9bd50 100644 --- a/toxcore/tox.c +++ b/toxcore/tox.c @@ -1533,6 +1533,7 @@ uint32_t tox_conference_new(Tox *tox, Tox_Err_Conference_New *error) bool tox_conference_delete(Tox *tox, uint32_t conference_number, Tox_Err_Conference_Delete *error) { Messenger *m = tox->m; + group_leave(m->conferences_object, conference_number); int ret = del_groupchat(m->conferences_object, conference_number); if (ret == -1) { -- cgit v1.2.3