From 6fa4089213f64baf9249ccfb8bfdd482f05de6bc Mon Sep 17 00:00:00 2001 From: isotoxin Date: Fri, 11 Nov 2016 14:56:14 +0300 Subject: [PATCH] Make conferences persistent across restarts and reconnects. This is not new groupchats. This is just upgrade to old groupchats with some advantages: - Groupchats are now saved into tox_save. - Clients can get groupchat unique id to save message log. - Auto restore groupchats after restart even your friend uses non-upgraded version. --- auto_tests/conference_test.c | 2 +- auto_tests/monolith_test.cpp | 9 +- toxav/groupav.c | 68 +- toxav/groupav.h | 4 +- toxav/toxav.api.h | 2 +- toxav/toxav.h | 2 +- toxav/toxav_old.c | 2 +- toxcore/Messenger.c | 44 +- toxcore/Messenger.h | 6 + toxcore/group.c | 3402 ++++++++++++++++++++++++---------- toxcore/group.h | 235 ++- toxcore/tox.api.h | 81 +- toxcore/tox.c | 71 +- toxcore/tox.h | 119 ++ 14 files changed, 2935 insertions(+), 1112 deletions(-) diff --git a/auto_tests/conference_test.c b/auto_tests/conference_test.c index 9e784e857d..bbb621161d 100644 --- a/auto_tests/conference_test.c +++ b/auto_tests/conference_test.c @@ -20,7 +20,7 @@ #include "helpers.h" -#define NUM_GROUP_TOX 5 +#define NUM_GROUP_TOX 16 #define GROUP_MESSAGE "Install Gentoo" static void handle_self_connection_status(Tox *tox, TOX_CONNECTION connection_status, void *user_data) diff --git a/auto_tests/monolith_test.cpp b/auto_tests/monolith_test.cpp index 50c599959d..834abd9266 100644 --- a/auto_tests/monolith_test.cpp +++ b/auto_tests/monolith_test.cpp @@ -226,9 +226,12 @@ int main(int argc, char *argv[]) // toxcore/friend_requests CHECK_SIZE(Friend_Requests, 1080); // toxcore/group - CHECK_SIZE(Group_c, 728); - CHECK_SIZE(Group_Chats, 2120); - CHECK_SIZE(Group_Peer, 480); + CHECK_SIZE(Group_c, 312); + CHECK_SIZE(Group_Chats, 80); + CHECK_SIZE(Group_Join_Peer, 48); + CHECK_SIZE(Group_Peer, 144); + CHECK_SIZE(Group_Peer_Lossy, 260); + CHECK_SIZE(jp_iterator, 40); // toxcore/list CHECK_SIZE(BS_LIST, 32); // toxcore/logger diff --git a/toxav/groupav.c b/toxav/groupav.c index e9595b4e8d..0b142cefc2 100644 --- a/toxav/groupav.c +++ b/toxav/groupav.c @@ -164,7 +164,7 @@ typedef struct { uint16_t audio_sequnum; - void (*audio_data)(Messenger *m, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, uint32_t samples, + void (*audio_data)(Messenger *m, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata); void *userdata; } Group_AV; @@ -225,7 +225,7 @@ static int recreate_encoder(Group_AV *group_av) } static Group_AV *new_group_av(Logger *log, Group_Chats *g_c, void (*audio_callback)(Messenger *, uint32_t, uint32_t, - const int16_t *, unsigned int, uint8_t, uint32_t, void *), void *userdata) + const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata) { if (!g_c) { return nullptr; @@ -246,7 +246,7 @@ static Group_AV *new_group_av(Logger *log, Group_Chats *g_c, void (*audio_callba return group_av; } -static void group_av_peer_new(void *object, uint32_t groupnumber, uint32_t friendgroupnumber) +static void group_av_peer_new(void *object, int groupnumber, int friendgroupnumber) { Group_AV *group_av = (Group_AV *)object; Group_Peer_AV *peer_av = (Group_Peer_AV *)calloc(1, sizeof(Group_Peer_AV)); @@ -259,7 +259,7 @@ static void group_av_peer_new(void *object, uint32_t groupnumber, uint32_t frien group_peer_set_object(group_av->g_c, groupnumber, friendgroupnumber, peer_av); } -static void group_av_peer_delete(void *object, uint32_t groupnumber, uint32_t friendgroupnumber, void *peer_object) +static void group_av_peer_delete(void *object, int groupnumber, void *peer_object) { Group_Peer_AV *peer_av = (Group_Peer_AV *)peer_object; @@ -275,15 +275,14 @@ static void group_av_peer_delete(void *object, uint32_t groupnumber, uint32_t fr free(peer_object); } -static void group_av_groupchat_delete(void *object, uint32_t groupnumber) +static void group_av_groupchat_delete(void *object, int groupnumber) { if (object) { kill_group_av((Group_AV *)object); } } -static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, uint32_t groupnumber, - uint32_t friendgroupnumber) +static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, int groupnumber, int friendgroupnumber) { if (!group_av || !peer_av) { return -1; @@ -388,7 +387,7 @@ static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, uint3 return -1; } -static int handle_group_audio_packet(void *object, uint32_t groupnumber, uint32_t friendgroupnumber, void *peer_object, +static int handle_group_audio_packet(void *object, int groupnumber, int friendgroupnumber, void *peer_object, const uint8_t *packet, uint16_t length) { if (!peer_object || !object || length <= sizeof(uint16_t)) { @@ -426,10 +425,13 @@ static int handle_group_audio_packet(void *object, uint32_t groupnumber, uint32_ * return 0 on success. * return -1 on failure. */ -static int groupchat_enable_av(Logger *log, Group_Chats *g_c, uint32_t groupnumber, void (*audio_callback)(Messenger *, - uint32_t, - uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), void *userdata) +static int groupchat_enable_av(Logger *log, Group_Chats *g_c, int groupnumber, void (*audio_callback)(Messenger *, + uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata) { + if (groupnumber == -1) { + return -1; + } + Group_AV *group_av = new_group_av(log, g_c, audio_callback, userdata); if (group_av == nullptr) { @@ -454,11 +456,10 @@ static int groupchat_enable_av(Logger *log, Group_Chats *g_c, uint32_t groupnumb * return -1 on failure. */ int add_av_groupchat(Logger *log, Group_Chats *g_c, void (*audio_callback)(Messenger *, uint32_t, uint32_t, - const int16_t *, - unsigned int, - uint8_t, uint32_t, void *), void *userdata) + const int16_t *, unsigned int, + uint8_t, unsigned int, void *), void *userdata) { - int groupnumber = add_groupchat(g_c, GROUPCHAT_TYPE_AV); + int groupnumber = add_groupchat(g_c, GROUPCHAT_TYPE_AV, nullptr); if (groupnumber == -1) { return -1; @@ -475,21 +476,21 @@ int add_av_groupchat(Logger *log, Group_Chats *g_c, void (*audio_callback)(Messe /* Join a AV group (you need to have been invited first.) * * returns group number on success - * returns -1 on failure. + * returns -1 .. -6 on failure (see join_groupchat) */ int join_av_groupchat(Logger *log, Group_Chats *g_c, uint32_t friendnumber, const uint8_t *data, uint16_t length, - void (*audio_callback)(Messenger *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), - void *userdata) + void (*audio_callback)(Messenger *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, unsigned int, + void *), void *userdata) { int groupnumber = join_groupchat(g_c, friendnumber, GROUPCHAT_TYPE_AV, data, length); - if (groupnumber == -1) { - return -1; + if (groupnumber < 0) { + return groupnumber; } if (groupchat_enable_av(log, g_c, groupnumber, audio_callback, userdata) == -1) { del_groupchat(g_c, groupnumber); - return -1; + return -5; /* initialization failed */ } return groupnumber; @@ -500,21 +501,32 @@ int join_av_groupchat(Logger *log, Group_Chats *g_c, uint32_t friendnumber, cons * return 0 on success. * return -1 on failure. */ -static int send_audio_packet(Group_Chats *g_c, uint32_t groupnumber, uint8_t *packet, uint16_t length) +static int send_audio_packet(Group_Chats *g_c, int groupnumber, uint8_t *packet, uint16_t length) { if (!length) { return -1; } - Group_AV *group_av = (Group_AV *)group_get_object(g_c, groupnumber); - VLA(uint8_t, data, 1 + sizeof(uint16_t) + length); + const size_t plen = 1 + sizeof(uint16_t) + length; + + if (plen > MAX_CRYPTO_DATA_SIZE) { + return -1; + } + + Group_AV *const group_av = (Group_AV *)group_get_object(g_c, groupnumber); + + if (!group_av) { + return -1; + } + + uint8_t data[MAX_CRYPTO_DATA_SIZE]; data[0] = GROUP_AUDIO_PACKET_ID; - uint16_t sequnum = net_htons(group_av->audio_sequnum); + const uint16_t sequnum = net_htons(group_av->audio_sequnum); memcpy(data + 1, &sequnum, sizeof(sequnum)); memcpy(data + 1 + sizeof(sequnum), packet, length); - if (send_group_lossy_packet(g_c, groupnumber, data, SIZEOF_VLA(data)) == -1) { + if (send_group_lossy_packet(g_c, groupnumber, data, (uint16_t)plen) == -1) { return -1; } @@ -527,8 +539,8 @@ static int send_audio_packet(Group_Chats *g_c, uint32_t groupnumber, uint8_t *pa * return 0 on success. * return -1 on failure. */ -int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, - uint32_t sample_rate) +int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, + uint8_t channels, unsigned int sample_rate) { Group_AV *group_av = (Group_AV *)group_get_object(g_c, groupnumber); diff --git a/toxav/groupav.h b/toxav/groupav.h index 142edb0ee0..920f81027c 100644 --- a/toxav/groupav.h +++ b/toxav/groupav.h @@ -47,6 +47,6 @@ int join_av_groupchat(Logger *log, Group_Chats *g_c, uint32_t friendnumber, cons * return 0 on success. * return -1 on failure. */ -int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, - uint32_t sample_rate); +int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, + uint8_t channels, uint32_t sample_rate); diff --git a/toxav/toxav.api.h b/toxav/toxav.api.h index 9e6cea6884..20b0f0fac2 100644 --- a/toxav/toxav.api.h +++ b/toxav/toxav.api.h @@ -622,7 +622,7 @@ int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void *, uint32_t, ui /* Join a AV group (you need to have been invited first.) * * returns group number on success - * returns -1 on failure. + * returns < 0 on failure. * * Audio data callback format (same as the one for toxav_add_av_groupchat()): * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) diff --git a/toxav/toxav.h b/toxav/toxav.h index 911d6b499c..2e853aad98 100644 --- a/toxav/toxav.h +++ b/toxav/toxav.h @@ -751,7 +751,7 @@ int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void *, uint32_t, ui /* Join a AV group (you need to have been invited first.) * * returns group number on success - * returns -1 on failure. + * returns < 0 on failure. * * Audio data callback format (same as the one for toxav_add_av_groupchat()): * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) diff --git a/toxav/toxav_old.c b/toxav/toxav_old.c index d527862381..100c3a9ed6 100644 --- a/toxav/toxav_old.c +++ b/toxav/toxav_old.c @@ -47,7 +47,7 @@ int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void *, uint32_t, ui /* Join a AV group (you need to have been invited first.) * * returns group number on success - * returns -1 on failure. + * returns < 0 on failure. * * Audio data callback format (same as the one for toxav_add_av_groupchat()): * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index 2bebae9235..fc9720f6f2 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c @@ -2744,6 +2744,7 @@ void do_messenger(Messenger *m, void *userdata) #define MESSENGER_STATE_TYPE_STATUS 6 #define MESSENGER_STATE_TYPE_TCP_RELAY 10 #define MESSENGER_STATE_TYPE_PATH_NODE 11 +#define MESSENGER_STATE_TYPE_CONFERENCES 100 #define MESSENGER_STATE_TYPE_END 255 #define SAVED_FRIEND_REQUEST_SIZE 1024 @@ -2956,6 +2957,26 @@ static int friends_list_load(Messenger *m, const uint8_t *data, uint32_t length) return num; } +// Empty definitions of the conference load/save functions. These are set if the +// groups module is loaded. In tox_new, the group module is initialised, so +// public API users will never see calls to these functions. +static uint32_t saved_conferences_size_default(const Messenger *m) +{ + return 0; +} +static void conferences_save_default(const Messenger *m, uint8_t *data) +{ +} +static int conferences_load_default(Messenger *m, const uint8_t *data, uint32_t length) +{ + return 0; +} + +uint32_t (*saved_conferences_size_ptr)(const Messenger *m) = saved_conferences_size_default; +void (*conferences_save_ptr)(const Messenger *m, uint8_t *data) = conferences_save_default; +int (*conferences_load_ptr)(Messenger *m, const uint8_t *data, uint32_t length) = conferences_load_default; + + /* return size of the messenger data (for saving) */ uint32_t messenger_size(const Messenger *m) { @@ -2969,6 +2990,7 @@ uint32_t messenger_size(const Messenger *m) + sizesubhead + 1 // status + sizesubhead + NUM_SAVED_TCP_RELAYS * packed_node_size(TCP_INET6) //TCP relays + sizesubhead + NUM_SAVED_PATH_NODES * packed_node_size(TCP_INET6) //saved path nodes + + sizesubhead + saved_conferences_size_ptr(m) // old group chats + sizesubhead; } @@ -3060,6 +3082,12 @@ void messenger_save(const Messenger *m, uint8_t *data) data += len; } + len = saved_conferences_size_ptr(m); + type = MESSENGER_STATE_TYPE_CONFERENCES; + data = messenger_save_subheader(data, len, type); + conferences_save_ptr(m, data); + data += len; + messenger_save_subheader(data, 0, MESSENGER_STATE_TYPE_END); } @@ -3138,6 +3166,20 @@ static int messenger_load_state_callback(void *outer, const uint8_t *data, uint3 break; } + case MESSENGER_STATE_TYPE_CONFERENCES: { + int err = conferences_load_ptr(m, data, length); + + /* No need to do something special if err < 0 + * err < 0 just means that conferences data was damaged + * and skipped + */ + if (err < 0) { + LOGGER_ERROR(m->log, "conference savedata was corrupted"); + } + + break; + } + case MESSENGER_STATE_TYPE_END: { if (length != 0) { return -1; @@ -3147,7 +3189,7 @@ static int messenger_load_state_callback(void *outer, const uint8_t *data, uint3 } default: - LOGGER_ERROR(m->log, "Load state: contains unrecognized part (len %u, type %u)\n", + LOGGER_ERROR(m->log, "Load state: contains unrecognized part (len %u, type %u)", length, type); break; } diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h index 402eb91650..bd28cdad13 100644 --- a/toxcore/Messenger.h +++ b/toxcore/Messenger.h @@ -778,4 +778,10 @@ uint32_t count_friendlist(const Messenger *m); * of out_list will be truncated to list_size. */ uint32_t copy_friendlist(const Messenger *m, uint32_t *out_list, uint32_t list_size); +// HACK HACK HACK to make conferences load/save work. +// TODO(robinlinden): Refactor this. +extern uint32_t (*saved_conferences_size_ptr)(const Messenger *m); +extern void (*conferences_save_ptr)(const Messenger *m, uint8_t *data); +extern int (*conferences_load_ptr)(Messenger *m, const uint8_t *data, uint32_t length); + #endif diff --git a/toxcore/group.c b/toxcore/group.c index 37b4437d79..46419ae0a2 100644 --- a/toxcore/group.c +++ b/toxcore/group.c @@ -27,111 +27,176 @@ #include "group.h" +#include "crypto_core.h" #include "util.h" -/* return 1 if the groupnumber is not valid. - * return 0 if the groupnumber is valid. +#include +#include +#include + +typedef enum { + UNS_NONE, + UNS_TEMP, + UNS_FOREVER, +} UnsubscribeType; + +/* Interval in seconds to send ping messages */ +#define GROUP_PING_INTERVAL 20 + +/** + * Packet type IDs as per the protocol specification. */ -static uint8_t groupnumber_not_valid(const Group_Chats *g_c, uint32_t groupnumber) +enum { + GROUP_MESSAGE_PING_ID = 0, + GROUP_MESSAGE_UNSUBSCRIBE_ID = 1, + GROUP_MESSAGE_NICKNAME_ID = 2, + GROUP_MESSAGE_NEW_PEER_ID = 16, + GROUP_MESSAGE_KILL_PEER_ID = 17, + GROUP_MESSAGE_NAME_ID = 48, + GROUP_MESSAGE_TITLE_ID = 49, +}; + +#define GROUP_MESSAGE_NEW_PEER_LENGTH (sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2) +#define GROUP_MESSAGE_KILL_PEER_LENGTH (sizeof(uint16_t)) + +#define MAX_GROUP_MESSAGE_DATA_LEN (MAX_CRYPTO_DATA_SIZE - (1 + MIN_MESSAGE_PACKET_LEN)) + +enum { + INVITE_ID = 0, + INVITE_RESPONSE_ID = 1, + INVITE_UNSUBSCRIBE_ID = 2, + INVITE_MYGROUP_ID = 3, +}; + +#define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) +#define INVITE_RESPONSE_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + GROUP_IDENTIFIER_LENGTH) + +#define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) + +enum { + PEER_KILL_ID = 1, + PEER_QUERY_ID = 8, + PEER_RESPONSE_ID = 9, + PEER_TITLE_ID = 10, + PEER_GROUP_NUM_ID = 11, +}; + +#define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1) + +#define MAX_FAILED_JOIN_ATTEMPTS 16 + +#define KEEP_JOIN_ATTEMPT_DELAY_MS 5000 +#define JOIN_ATTEMPT_DELAY_MS 20000 +#define JOIN_CHECK_DELAY_MS 333 + +// friendconn_id for when the friend is "almost deleted" +// It just marks the friend as "deleted", not really deleted. See: apply_changes_in_peers. +#define ALMOST_DELETED_PEER (-2) + +#define PEER_INDEX_MAX (UINT16_MAX - 1) +#define INVALID_PEER_INDEX UINT16_MAX +#define INVALID_GROUP_NUMBER 0xffff + +static void group_name_send(const Group_Chats *g_c, int32_t groupnumber, const uint8_t *nick, uint8_t nick_len); +static int64_t send_packet_online(Friend_Connections *fr_c, int friendcon_id, + uint16_t group_num, const uint8_t *identifier); +static uint32_t send_lossy_all_close(const Group_Chats *g_c, int32_t groupnumber, const uint8_t *data, uint16_t length, + uint16_t receiver); +static uint32_t send_message_all_close(const Group_Chats *g_c, int32_t groupnumber, const uint8_t *data, + uint16_t length, uint16_t except_peer); +static int64_t send_message_group(const Group_Chats *g_c, int32_t groupnumber, uint8_t message_id, + const uint8_t *data, uint16_t len); +static void send_peer_query(const Group_Chats *g_c, int friendcon_id, uint16_t other_group_num); +static void handle_direct_packet(Group_Chats *g_c, int32_t groupnumber, const uint8_t *data, uint16_t length, + int64_t peer_index); +static void send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t group_num); +static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); +static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); +static void send_peers(Group_Chats *g_c, int32_t groupnumber, int friendcon_id, uint16_t other_group_num); +static void send_peer_nums(const Group_Chats *g_c, int32_t groupnumber, int friendcon_id, uint16_t other_group_num); +static int get_self_peer_gid(const Group_c *g); +static int conference_unsubscribe(const Group_Chats *g_c, int32_t groupnumber, UnsubscribeType u); +static Group_c *group_kill_peer_send(const Group_Chats *g_c, int32_t groupnumber); +static int8_t group_packet_index(uint8_t msg_id); + +static bool really_connected(const Group_Peer *peer) { - if (groupnumber >= g_c->num_chats) { - return 1; - } + return peer->connected && peer->friendcon_id >= 0 && peer->group_number != INVALID_GROUP_NUMBER; +} - if (g_c->chats == nullptr) { - return 1; +static bool is_groupnumber_valid(const Group_Chats *g_c, int32_t groupnumber) +{ + // This function will receive -1 if get_group_num can't find the conference. + if (groupnumber < 0 || groupnumber >= g_c->num_chats) { + return false; } - if (g_c->chats[groupnumber].status == GROUPCHAT_STATUS_NONE) { - return 1; + if (!g_c->chats) { + return false; } - return 0; + return g_c->chats[groupnumber].live; } /* Set the size of the groupchat list to num. * - * return -1 if realloc fails. - * return 0 if it succeeds. + * return false if realloc fails. + * return true if it succeeds. */ -static int realloc_groupchats(Group_Chats *g_c, uint32_t num) +static bool realloc_conferences(Group_Chats *g_c, uint16_t num) { if (num == 0) { free(g_c->chats); g_c->chats = nullptr; - return 0; + return true; } Group_c *newgroup_chats = (Group_c *)realloc(g_c->chats, num * sizeof(Group_c)); if (newgroup_chats == nullptr) { - return -1; + return false; } g_c->chats = newgroup_chats; - return 0; + return true; } +static void setup_conference(Group_c *g) +{ + memset(g, 0, sizeof(Group_c)); + g->keep_join_index = -1; + g->live = true; +} /* Create a new empty groupchat connection. * * return -1 on failure. * return groupnumber on success. */ -static int create_group_chat(Group_Chats *g_c) +static int32_t create_group_chat(Group_Chats *g_c) { - uint32_t i; - - for (i = 0; i < g_c->num_chats; ++i) { - if (g_c->chats[i].status == GROUPCHAT_STATUS_NONE) { + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (!g_c->chats[i].live) { + setup_conference(&g_c->chats[i]); return i; } } - int id = -1; + int32_t id = -1; - if (realloc_groupchats(g_c, g_c->num_chats + 1) == 0) { + if (realloc_conferences(g_c, g_c->num_chats + 1)) { id = g_c->num_chats; ++g_c->num_chats; - memset(&g_c->chats[id], 0, sizeof(Group_c)); + setup_conference(&g_c->chats[id]); } return id; } - -/* Wipe a groupchat. - * - * return -1 on failure. - * return 0 on success. - */ -static int wipe_group_chat(Group_Chats *g_c, uint32_t groupnumber) -{ - if (groupnumber_not_valid(g_c, groupnumber)) { - return -1; - } - - uint32_t i; - crypto_memzero(&g_c->chats[groupnumber], sizeof(Group_c)); - - for (i = g_c->num_chats; i != 0; --i) { - if (g_c->chats[i - 1].status != GROUPCHAT_STATUS_NONE) { - break; - } - } - - if (g_c->num_chats != i) { - g_c->num_chats = i; - realloc_groupchats(g_c, g_c->num_chats); - } - - return 0; -} - -static Group_c *get_group_c(const Group_Chats *g_c, uint32_t groupnumber) +static Group_c *get_group_c(const Group_Chats *g_c, int32_t groupnumber) { - if (groupnumber_not_valid(g_c, groupnumber)) { + if (!is_groupnumber_valid(g_c, groupnumber)) { return nullptr; } @@ -147,12 +212,10 @@ static Group_c *get_group_c(const Group_Chats *g_c, uint32_t groupnumber) * TODO(irungentoo): make this more efficient. */ -static int peer_in_chat(const Group_c *chat, const uint8_t *real_pk) +static int64_t peer_in_chat(const Group_c *chat, const uint8_t *real_pk) { - uint32_t i; - - for (i = 0; i < chat->numpeers; ++i) { - if (id_equal(chat->group[i].real_pk, real_pk)) { + for (uint32_t i = 0; i < chat->numpeers; ++i) { + if (chat->peers[i].friendcon_id != ALMOST_DELETED_PEER && id_equal(chat->peers[i].real_pk, real_pk)) { return i; } } @@ -166,14 +229,27 @@ static int peer_in_chat(const Group_c *chat, const uint8_t *real_pk) * return group number if peer is in list. * return -1 if group is not in list. * - * TODO(irungentoo): make this more efficient and maybe use constant time comparisons? + * TODO(irungentoo): make this more efficient. */ -static int get_group_num(const Group_Chats *g_c, const uint8_t *identifier) +static int32_t get_group_num(const Group_Chats *g_c, const uint8_t *identifier) { - uint32_t i; + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (g_c->chats[i].live && crypto_memcmp(g_c->chats[i].identifier, identifier, GROUP_IDENTIFIER_LENGTH) == 0) { + return i; + } + } + + return -1; +} + +int conference_by_uid(const Group_Chats *g_c, const uint8_t *uid) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (!g_c->chats[i].live) { + continue; + } - for (i = 0; i < g_c->num_chats; ++i) { - if (crypto_memcmp(g_c->chats[i].identifier, identifier, GROUP_IDENTIFIER_LENGTH) == 0) { + if (crypto_memcmp(g_c->chats[i].identifier + 1, uid, GROUP_IDENTIFIER_LENGTH - 1) == 0) { return i; } } @@ -187,14 +263,11 @@ static int get_group_num(const Group_Chats *g_c, const uint8_t *identifier) * return peer number if peer is in chat. * return -1 if peer is not in chat. * - * TODO(irungentoo): make this more efficient. */ -static int get_peer_index(Group_c *g, uint16_t peer_number) +static int64_t get_peer_index(const Group_c *g, uint16_t peer_gid) { - uint32_t i; - - for (i = 0; i < g->numpeers; ++i) { - if (g->group[i].peer_number == peer_number) { + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->peers[i].gid == (int)peer_gid) { return i; } } @@ -202,525 +275,725 @@ static int get_peer_index(Group_c *g, uint16_t peer_number) return -1; } +static uint16_t find_new_peer_gid(const Group_c *g) +{ + uint16_t peer_number = random_u16(); + size_t tries = 0; + + while (get_peer_index(g, peer_number) != -1) { + peer_number = random_u16(); + ++tries; + + if (tries > 32) { + /* just find first unused peer number */ + peer_number = 0; + + while (get_peer_index(g, peer_number) != -1) { + ++peer_number; + } + + break; + } + } + + return peer_number; +} static uint64_t calculate_comp_value(const uint8_t *pk1, const uint8_t *pk2) { uint64_t cmp1 = 0, cmp2 = 0; - unsigned int i; - - for (i = 0; i < sizeof(uint64_t); ++i) { + for (size_t i = 0; i < sizeof(uint64_t); ++i) { cmp1 = (cmp1 << 8) + (uint64_t)pk1[i]; cmp2 = (cmp2 << 8) + (uint64_t)pk2[i]; } - return (cmp1 - cmp2); + return cmp1 - cmp2; } -enum { - GROUPCHAT_CLOSEST_NONE, - GROUPCHAT_CLOSEST_ADDED, - GROUPCHAT_CLOSEST_REMOVED -}; - -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) +static bool closest(const Group_c *g, const uint8_t i) { - Group_c *g = get_group_c(g_c, groupnumber); + assert(i < DESIRED_CLOSE_CONNECTIONS); + return (g->closest_peers_entry & (1ul << i)) != 0; +} - if (!g) { - return -1; - } +static void set_closest(Group_c *g, const uint8_t i) +{ + assert(i < DESIRED_CLOSE_CONNECTIONS); + g->closest_peers_entry |= (1ul << i); +} - if (public_key_cmp(g->real_pk, real_pk) == 0) { - return -1; - } - unsigned int i; - unsigned int index = DESIRED_CLOSE_CONNECTIONS; +static void add_closest(Group_c *g, const uint16_t peerindex) +{ + for (uint8_t i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (!closest(g, i)) { - for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { - if (g->closest_peers[i].entry && public_key_cmp(real_pk, g->closest_peers[i].real_pk) == 0) { - return 0; + g->closest_peers[i] = peerindex; + set_closest(g, i); + return; } } - for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { - if (g->closest_peers[i].entry == 0) { - index = i; - break; - } - } + uint8_t index = DESIRED_CLOSE_CONNECTIONS; - if (index == DESIRED_CLOSE_CONNECTIONS) { - uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); - uint64_t comp_d = 0; + const uint8_t *real_pk = g->peers[peerindex].real_pk; + uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); + uint64_t comp_d = 0; - for (i = 0; i < (DESIRED_CLOSE_CONNECTIONS / 2); ++i) { - uint64_t comp; - comp = calculate_comp_value(g->real_pk, g->closest_peers[i].real_pk); + for (uint8_t i = 0; i < (DESIRED_CLOSE_CONNECTIONS / 2); ++i) { + const uint64_t comp = calculate_comp_value(g->real_pk, g->peers[g->closest_peers[i]].real_pk); - if (comp > comp_val && comp > comp_d) { - index = i; - comp_d = comp; - } + if (comp > comp_val && comp > comp_d) { + index = i; + comp_d = comp; } + } - comp_val = calculate_comp_value(real_pk, g->real_pk); + comp_val = calculate_comp_value(real_pk, g->real_pk); - for (i = (DESIRED_CLOSE_CONNECTIONS / 2); i < DESIRED_CLOSE_CONNECTIONS; ++i) { - uint64_t comp = calculate_comp_value(g->closest_peers[i].real_pk, g->real_pk); + for (uint8_t i = (DESIRED_CLOSE_CONNECTIONS / 2); i < DESIRED_CLOSE_CONNECTIONS; ++i) { + const uint64_t comp = calculate_comp_value(g->peers[g->closest_peers[i]].real_pk, g->real_pk); - if (comp > comp_val && comp > comp_d) { - index = i; - comp_d = comp; - } + if (comp > comp_val && comp > comp_d) { + index = i; + comp_d = comp; } } - if (index == DESIRED_CLOSE_CONNECTIONS) { - return -1; - } - - uint8_t old_real_pk[CRYPTO_PUBLIC_KEY_SIZE]; - uint8_t old_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; - uint8_t old = 0; + if (index < DESIRED_CLOSE_CONNECTIONS) { + assert(index < DESIRED_CLOSE_CONNECTIONS); - if (g->closest_peers[index].entry) { - memcpy(old_real_pk, g->closest_peers[index].real_pk, CRYPTO_PUBLIC_KEY_SIZE); - memcpy(old_temp_pk, g->closest_peers[index].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); - old = 1; - } - - g->closest_peers[index].entry = 1; - memcpy(g->closest_peers[index].real_pk, real_pk, CRYPTO_PUBLIC_KEY_SIZE); - memcpy(g->closest_peers[index].temp_pk, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + uint16_t rmpeer = g->closest_peers[index]; + g->closest_peers[index] = peerindex; + g->closest_peers_entry |= 1ul << index; - if (old) { - add_to_closest(g_c, groupnumber, old_real_pk, old_temp_pk); + add_closest(g, rmpeer); } +} - if (!g->changed) { - g->changed = GROUPCHAT_CLOSEST_ADDED; +static int g_handle_status(void *object, int friendcon_id, uint8_t status, void *userdata) +{ + if (status) { + return 0; } - return 0; -} + Group_Chats *g_c = (Group_Chats *)object; -static unsigned int pk_in_closest_peers(Group_c *g, uint8_t *real_pk) -{ - unsigned int i; + for (uint16_t ig = 0; ig < g_c->num_chats; ++ig) { + Group_c *g = get_group_c(g_c, ig); - for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { - if (!g->closest_peers[i].entry) { + if (!g) { continue; } - if (public_key_cmp(g->closest_peers[i].real_pk, real_pk) == 0) { - return 1; + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->peers[i].friendcon_id == friendcon_id) { + g->peers[i].friendcon_id = -1; + g->peers[i].group_number = INVALID_GROUP_NUMBER; + g->peers[i].connected = false; + } } } return 0; } -static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t *identifier); - -static int connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *userdata) +static void process_dirty_list(Group_Chats *g_c, Group_c *g, int32_t groupnumber, void *userdata) { - Group_c *g = get_group_c(g_c, groupnumber); + bool some_changes = false; + size_t empty_slots_in_list = 0; - if (!g) { - return -1; - } + /* detect almost deleted peers and prepare peers_list slots for delete */ + for (uint32_t i = 0; i < g->numpeers_in_list; ++i) { + uint32_t peer_index = g->peers_list[i]; + const Group_Peer *peer = &g->peers[peer_index]; - if (!g->changed) { - return 0; + if (peer->friendcon_id == ALMOST_DELETED_PEER || peer->gid < 0) { + some_changes = true; + g->peers_list[i] = INVALID_PEER_INDEX; + ++empty_slots_in_list; + } } - unsigned int i; + uint32_t new_size_of_list = 0; - if (g->changed == GROUPCHAT_CLOSEST_REMOVED) { - for (i = 0; i < g->numpeers; ++i) { - add_to_closest(g_c, groupnumber, g->group[i].real_pk, g->group[i].temp_pk); - } - } + /* Put new peers to just deleted slots */ + for (uint32_t i = 0; i < g->numpeers; ++i) { + const Group_Peer *peer = &g->peers[i]; - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { + if (peer->friendcon_id == ALMOST_DELETED_PEER || peer->gid < 0) { continue; } - if (!g->close[i].closest) { + ++new_size_of_list; + + if (empty_slots_in_list == 0) { continue; } - uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; - uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; - get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[i].number); + int64_t empty_slot = -1; + bool present = false; + + for (uint32_t j = 0; j < g->numpeers_in_list; ++j) { + if (empty_slot < 0 && g->peers_list[j] == INVALID_PEER_INDEX) { + empty_slot = j; + } + + if (g->peers_list[j] == i) { + present = true; + } + + if (empty_slot >= 0 && present) { + break; + } + } - if (!pk_in_closest_peers(g, real_pk)) { - g->close[i].type = GROUPCHAT_CLOSE_NONE; - kill_friend_connection(g_c->fr_c, g->close[i].number); + if (!present) { + g->peers_list[empty_slot] = (uint16_t)i; + --empty_slots_in_list; + some_changes = true; } } - for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { - if (!g->closest_peers[i].entry) { - continue; - } + if (empty_slots_in_list > 0) { + /* all new peers were put into empty slots, so there are no new peers */ + /* just remove empty slots both in peers_list and peers*/ - int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->closest_peers[i].real_pk); + /* remove empty slots in peers_list */ + for (uint32_t j = 0; j < g->numpeers_in_list;) { + if (g->peers_list[j] != INVALID_PEER_INDEX) { + ++j; + continue; + } - uint8_t lock = 1; + --g->numpeers_in_list; + /* g->peers_list[j] = g->peers_list[g->numpeers_in_list]; */ /* faster */ + // TODO(iphydf): Why is this "accurate"? + memmove(&g->peers_list[j], &g->peers_list[j + 1], + sizeof(uint16_t) * (g->numpeers_in_list - j)); /* slower, but accurate */ + some_changes = true; + } - if (friendcon_id == -1) { - friendcon_id = new_friend_connection(g_c->fr_c, g->closest_peers[i].real_pk); - lock = 0; + /* remove empty slots in peers */ + for (uint32_t i = 0; i < g->numpeers;) { + Group_Peer *peer = &g->peers[i]; - if (friendcon_id == -1) { + if (peer->friendcon_id != ALMOST_DELETED_PEER) { + ++i; continue; } - set_dht_temp_pk(g_c->fr_c, friendcon_id, g->closest_peers[i].temp_pk, userdata); + --g->numpeers; + memcpy(peer, &g->peers[g->numpeers], sizeof(Group_Peer)); + + /* fix index in peers_list */ + for (uint32_t j = 0; j < g->numpeers_in_list; ++j) { + if (g->peers_list[j] == g->numpeers) { + g->peers_list[j] = (uint16_t)i; + break; + } + } } + } else if (new_size_of_list > g->numpeers_in_list) { + /* Expand peers_list and put indexes of new peers into it */ + uint16_t *temp = (uint16_t *)realloc(g->peers_list, sizeof(uint16_t) * new_size_of_list); - add_conn_to_groupchat(g_c, friendcon_id, groupnumber, 1, lock); + if (temp) { + g->peers_list = temp; - if (friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { - send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); - } - } + for (uint32_t i = 0; i < g->numpeers; ++i) { + const Group_Peer *peer = &g->peers[i]; - g->changed = GROUPCHAT_CLOSEST_NONE; + if (peer->friendcon_id == ALMOST_DELETED_PEER || peer->gid < 0) { + continue; + } - return 0; -} + bool present = false; -/* Add a peer to the group chat. - * - * 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 - * from outside of the tox_iterate() loop. - * - * return peer_index if success or peer already in chat. - * 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) -{ - Group_c *g = get_group_c(g_c, groupnumber); + for (uint32_t j = 0; j < g->numpeers_in_list; ++j) { + if (g->peers_list[j] == i) { + present = true; + break; + } + } - if (!g) { - return -1; + if (!present) { + g->peers_list[g->numpeers_in_list] = (uint16_t)i; + ++g->numpeers_in_list; + some_changes = true; + } + } + } } - // TODO(irungentoo): - int peer_index = peer_in_chat(g, real_pk); + /* Now notify client by calling callbacks */ + if (some_changes) { + if (!g->invite_called && !g->join_mode && g_c->invite_callback) { + g->fake_join = true; + g_c->invite_callback(g_c->m, UINT32_MAX, g->identifier[0], g->identifier + 1, GROUP_IDENTIFIER_LENGTH - 1, userdata); + g->fake_join = false; - if (peer_index != -1) { - id_copy(g->group[peer_index].temp_pk, temp_pk); + if (!g->invite_called) { + del_groupchat(g_c, (int)groupnumber); + return; + } + } - if (g->group[peer_index].peer_number != peer_number) { - return -1; + if (g_c->peer_list_changed_callback != nullptr) { + g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); } - return peer_index; + g->nick_changed = false; } - peer_index = get_peer_index(g, peer_number); - - if (peer_index != -1) { - return -1; - } + /* and now rebuild closest */ - Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); + uint16_t old_closest_peers[DESIRED_CLOSE_CONNECTIONS]; + uint8_t inclose = 0; - if (temp == nullptr) { - return -1; + for (uint8_t i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (closest(g, i)) { + old_closest_peers[inclose++] = g->closest_peers[i]; + } } - memset(&temp[g->numpeers], 0, sizeof(Group_Peer)); - g->group = temp; + g->closest_peers_entry = 0; - id_copy(g->group[g->numpeers].real_pk, real_pk); - id_copy(g->group[g->numpeers].temp_pk, temp_pk); - g->group[g->numpeers].peer_number = peer_number; + uint32_t np = g->numpeers; - g->group[g->numpeers].last_recv = unix_time(); - ++g->numpeers; + for (uint32_t i = 0; i < np; ++i) { + const Group_Peer *peer = &g->peers[i]; - add_to_closest(g_c, groupnumber, real_pk, temp_pk); + if (peer->friendcon_id == ALMOST_DELETED_PEER || peer->auto_join || id_equal(g->real_pk, peer->real_pk)) { + continue; + } - if (do_gc_callback && g_c->peer_list_changed_callback) { - g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); + add_closest(g, i); } - if (g->peer_on_join) { - g->peer_on_join(g->object, groupnumber, g->numpeers - 1); - } + for (uint8_t i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (closest(g, i)) { + bool just_added = true; + + for (uint8_t j = 0; j < inclose; ++j) { + if (g->closest_peers[i] == old_closest_peers[j]) { + just_added = false; + break; + } + } - return (g->numpeers - 1); + const Group_Peer *peer = &g->peers[g->closest_peers[i]]; + + if (just_added && peer->connected) { + send_packet_online(g_c->fr_c, peer->friendcon_id, (uint16_t)groupnumber, g->identifier); + } + } + } } -static int remove_close_conn(Group_Chats *g_c, uint32_t groupnumber, int friendcon_id) +static void apply_changes_in_peers(Group_Chats *g_c, int32_t groupnumber, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { - return -1; + return; } - uint32_t i; + if (g->dirty_list) { + process_dirty_list(g_c, g, groupnumber, userdata); + g->dirty_list = false; + } - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { - continue; - } + /* notify client about nick change */ + if (g->nick_changed) { + for (uint32_t i = 0; i < g->numpeers_in_list; ++i) { + Group_Peer *peer = &g->peers[g->peers_list[i]]; - if (g->close[i].number == (unsigned int)friendcon_id) { - g->close[i].type = GROUPCHAT_CLOSE_NONE; - kill_friend_connection(g_c->fr_c, friendcon_id); - return 0; + if (peer->nick_changed) { + if (g_c->peer_name_callback != nullptr) { + g_c->peer_name_callback(g_c->m, groupnumber, i, peer->nick, peer->nick_len, userdata); + } + + peer->nick_changed = false; + } } + + g->nick_changed = false; } - return -1; -} + /* notify client about title change */ + if (g->title_changed && g_c->title_callback) { + bool cb = false; + for (uint32_t i = 0; i < g->numpeers_in_list; ++i) { + Group_Peer *peer = &g->peers[g->peers_list[i]]; -/* - * 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) + if (peer->title_changed) { + + if (!cb) { + g_c->title_callback(g_c->m, (int)groupnumber, (int)i, g->title, g->title_len, userdata); + } + + peer->title_changed = false; + cb = true; + } + } + + if (!cb) { + g_c->title_callback(g_c->m, (int)groupnumber, -1, g->title, g->title_len, userdata); + } + + g->title_changed = false; + } +} + +static void connect_to_closest(Group_Chats *g_c, int32_t groupnumber, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { - return -1; + return; } - uint32_t i; + if (g->dirty_list) { + return; + } - 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; + if (!g->join_mode && is_timeout(g->last_close_check_time, 5)) { + /* kill connections to non closest */ + + g->last_close_check_time = unix_time(); + size_t nconnected = 0; + + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->peers[i].connected) { + ++nconnected; + } } - } - int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->group[peer_index].real_pk); + if (nconnected > DESIRED_CLOSE_CONNECTIONS) { + for (uint32_t i = 0; i < g->numpeers; ++i) { + Group_Peer *peer = &g->peers[i]; - if (friendcon_id != -1) { - remove_close_conn(g_c, groupnumber, friendcon_id); - } + if (peer->friendcon_id < 0 || peer->keep_connection) { + continue; + } - --g->numpeers; + uint8_t k; - void *peer_object = g->group[peer_index].object; + for (k = 0; k < DESIRED_CLOSE_CONNECTIONS; ++k) { + if (closest(g, k) && g->closest_peers[k] == i) { + k = DESIRED_CLOSE_CONNECTIONS + 100; + break; + } + } - if (g->numpeers == 0) { - free(g->group); - g->group = nullptr; - } else { - if (g->numpeers != (uint32_t)peer_index) { - memcpy(&g->group[peer_index], &g->group[g->numpeers], sizeof(Group_Peer)); - } + if (k == DESIRED_CLOSE_CONNECTIONS + 100) { + continue; + } - Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers)); + if (really_connected(peer)) { + send_peer_kill(g_c, peer->friendcon_id, peer->group_number); + } - if (temp == nullptr) { - return -1; + kill_friend_connection(g_c->fr_c, peer->friendcon_id); + peer->friendcon_id = -1; + peer->group_number = INVALID_GROUP_NUMBER; + peer->connected = false; + } } - - g->group = temp; } - if (g_c->peer_list_changed_callback) { - g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); - } + for (uint8_t i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (!closest(g, i)) { + continue; + } - if (g->peer_on_leave) { - g->peer_on_leave(g->object, groupnumber, peer_index, peer_object); - } + Group_Peer *peer = &g->peers[g->closest_peers[i]]; - return 0; -} + if (peer->friendcon_id < 0) { + int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, peer->real_pk); + bool addref = true; -/* Set the nick for a peer. - * - * 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 - * from outside of the tox_iterate() loop. - * - * return 0 on success. - * return -1 if error. - */ -static int setnick(Group_Chats *g_c, uint32_t groupnumber, int peer_index, const uint8_t *nick, uint16_t nick_len, - void *userdata, bool do_gc_callback) -{ - if (nick_len > MAX_NAME_LENGTH) { - return -1; + if (friendcon_id < 0) { + friendcon_id = new_friend_connection(g_c->fr_c, peer->real_pk); + addref = false; + + if (friendcon_id == -1) { + continue; + } + + set_dht_temp_pk(g_c->fr_c, friendcon_id, peer->temp_pk, userdata); + } + + if (addref) { + friend_connection_lock(g_c->fr_c, friendcon_id); + } + + peer->friendcon_id = friendcon_id; + + 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); + } + + if (friend_con_connected(g_c->fr_c, peer->friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { + if (!peer->connected) { + peer->connected = true; + peer->need_send_peers = true; + g->need_send_name = true; + send_packet_online(g_c->fr_c, peer->friendcon_id, (uint16_t)groupnumber, g->identifier); + } + } else { + peer->connected = false; + } + + if (peer->need_send_peers && really_connected(peer)) { + send_peers(g_c, groupnumber, peer->friendcon_id, peer->group_number); + send_peer_nums(g_c, groupnumber, peer->friendcon_id, peer->group_number); + peer->need_send_peers = false; + } } - Group_c *g = get_group_c(g_c, groupnumber); - if (!g) { - return -1; + if (g->need_send_name) { + group_name_send(g_c, groupnumber, g_c->m->name, g_c->m->name_length); } +} - /* same name as already stored? */ - if (g->group[peer_index].nick_len == nick_len) { - if (nick_len == 0 || !memcmp(g->group[peer_index].nick, nick, nick_len)) { - return 0; - } +static void addjoinpeer(Group_c *g, const uint8_t *real_pk) +{ + if (id_equal(g->real_pk, real_pk)) { + return; } - if (nick_len) { - memcpy(g->group[peer_index].nick, nick, nick_len); + bool already_here = false; + + for (uint16_t i = 0; i < g->numjoinpeers; ++i) { + if (!already_here && id_equal(g->joinpeers[i].real_pk, real_pk)) { + already_here = true; + g->joinpeers[i].unsubscribed = false; + } + + g->joinpeers[i].fails = MAX_FAILED_JOIN_ATTEMPTS; } - g->group[peer_index].nick_len = nick_len; + if (!already_here) { + Group_Join_Peer *temp = (Group_Join_Peer *)realloc(g->joinpeers, sizeof(Group_Join_Peer) * (g->numjoinpeers + 1)); + + if (temp == nullptr) { + return; + } + + g->joinpeers = temp; + temp = g->joinpeers + g->numjoinpeers; + ++g->numjoinpeers; - if (do_gc_callback && g_c->peer_name_callback) { - g_c->peer_name_callback(g_c->m, groupnumber, peer_index, nick, nick_len, userdata); + memset(temp, 0, sizeof(Group_Join_Peer)); + id_copy(temp->real_pk, real_pk); + temp->fails = MAX_FAILED_JOIN_ATTEMPTS; } +} - return 0; +static void need_send_peers(Group_c *g) +{ + for (uint32_t i = 0; i < g->numpeers; ++i) { + g->peers[i].need_send_peers = true; + } } -static int settitle(Group_Chats *g_c, uint32_t groupnumber, int peer_index, const uint8_t *title, uint8_t title_len, - void *userdata) +static int64_t addpeer(Group_c *g, int32_t groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk, int peer_gid) { - if (title_len > MAX_NAME_LENGTH || title_len == 0) { + if (peer_gid >= 0) { + addjoinpeer(g, real_pk); + } + + int64_t peer_index = peer_in_chat(g, real_pk); + + if (peer_index != -1) { + id_copy(g->peers[peer_index].temp_pk, temp_pk); + + if (peer_gid != g->peers[peer_index].gid) { + g->dirty_list = true; + + if (peer_gid >= 0) { + need_send_peers(g); + } + + g->peers[peer_index].gid = peer_gid; + + if (peer_gid >= 0) { + g->peers[peer_index].auto_join = false; + } + } + + return peer_index; + } + + peer_index = get_peer_index(g, peer_gid); + + if (peer_index != -1) { return -1; } - Group_c *g = get_group_c(g_c, groupnumber); + Group_Peer *new_peers = (Group_Peer *)realloc(g->peers, sizeof(Group_Peer) * (g->numpeers + 1)); - if (!g) { + if (new_peers == nullptr) { return -1; } - /* same as already set? */ - if (g->title_len == title_len && !memcmp(g->title, title, title_len)) { - return 0; + g->peers = new_peers; + new_peers = new_peers + g->numpeers; + ++g->numpeers; + + /* Group_Peer constructor */ + + memset(new_peers, 0, sizeof(Group_Peer)); + + /* set undefined */ + new_peers->friendcon_id = -1; + + /* set undefined for other */ + new_peers->group_number = id_equal(g->real_pk, real_pk) ? (uint16_t)groupnumber : INVALID_GROUP_NUMBER; + + id_copy(new_peers->real_pk, real_pk); + id_copy(new_peers->temp_pk, temp_pk); + + if (peer_gid >= 0) { + need_send_peers(g); + new_peers->gid = peer_gid; + } else { + new_peers->gid = -1; } - memcpy(g->title, title, title_len); - g->title_len = title_len; + new_peers->last_recv = unix_time(); - if (g_c->title_callback) { - g_c->title_callback(g_c->m, groupnumber, peer_index, title, title_len, userdata); + g->dirty_list = true; + + if (g->peer_on_join) { + g->peer_on_join(g->object, groupnumber, g->numpeers - 1); } - return 0; + return g->numpeers - 1; +} + +static void free_peer_members(Group_Peer *peer) +{ + free(peer->lossy); + peer->lossy = nullptr; + + free(peer->nick); + peer->nick = nullptr; } -static void set_conns_type_close(Group_Chats *g_c, uint32_t groupnumber, int friendcon_id, uint8_t type) +/* + * Delete a peer from the group chat. + * + * return true if success + * return false if error. + */ +static bool delpeer(Group_Chats *g_c, int32_t groupnumber, uint16_t peer_index) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { - return; + return false; } - uint32_t i; + Group_Peer *peer = &g->peers[peer_index]; - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { - continue; - } + if (peer->friendcon_id >= 0) { + kill_friend_connection(g_c->fr_c, peer->friendcon_id); + } - if (g->close[i].number != (unsigned int)friendcon_id) { - continue; - } + peer->friendcon_id = ALMOST_DELETED_PEER; + peer->connected = false; + peer->group_number = INVALID_GROUP_NUMBER; + peer->gid = -1; + free_peer_members(peer); - if (type == GROUPCHAT_CLOSE_ONLINE) { - send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); - } else { - g->close[i].type = type; - } - } -} -/* 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) -{ - uint32_t i; + g->dirty_list = true; - for (i = 0; i < g_c->num_chats; ++i) { - set_conns_type_close(g_c, i, friendcon_id, type); + if (g->peer_on_leave) { + g->peer_on_leave(g->object, groupnumber, peer->object); } + + peer->object = nullptr; + + return true; } -static int g_handle_status(void *object, int friendcon_id, uint8_t status, void *userdata) +static int find_peer_index_in_list(const Group_c *g, uint16_t peer_index) { - Group_Chats *g_c = (Group_Chats *)object; - - if (status) { /* Went online */ - set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_ONLINE); - } else { /* Went offline */ - set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_CONNECTION); - // TODO(irungentoo): remove timedout connections? + for (uint32_t i = 0; i < g->numpeers_in_list; ++i) { + if (g->peers_list[i] == peer_index) { + assert(i <= INT_MAX); + return (int)i; + } } - return 0; + return -1; } -static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); -static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); - -/* Add friend to group chat. +/* Set the nick for a peer. * - * return close index on success - * return -1 on failure. + * return true on success. + * return false if error. */ -static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t groupnumber, uint8_t closest, - uint8_t lock) +static bool setnick(Group_c *g, int64_t peer_index, const uint8_t *nick, uint8_t nick_len) { - Group_c *g = get_group_c(g_c, groupnumber); + if (nick_len > MAX_NAME_LENGTH || nick_len == 0) { + return false; + } - if (!g) { - return -1; + Group_Peer *peer = &g->peers[peer_index]; + + /* same name as already stored? */ + if (peer->nick_len == nick_len && memcmp(peer->nick, nick, nick_len) == 0) { + return true; } - uint16_t i, ind = MAX_GROUP_CONNECTIONS; + if (nick_len) { + if (nick_len > peer->nick_len) { + uint8_t *new_nick = (uint8_t *)realloc(peer->nick, nick_len + 1); + + if (!new_nick) { + return false; + } - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { - ind = i; - continue; + peer->nick = new_nick; + peer->nick[nick_len] = 0; } - if (g->close[i].number == (uint32_t)friendcon_id) { - g->close[i].closest = closest; - return i; /* Already in list. */ - } + memcpy(peer->nick, nick, nick_len); } - if (ind == MAX_GROUP_CONNECTIONS) { - return -1; + peer->nick_len = nick_len; + peer->nick_changed = true; + g->nick_changed = true; + return true; +} + +static bool settitle(Group_c *g, int64_t peer_index, const uint8_t *title, uint8_t title_len) +{ + if (title_len > MAX_NAME_LENGTH || title_len == 0) { + return false; } - if (lock) { - friend_connection_lock(g_c->fr_c, friendcon_id); + /* same as already set? */ + if (g->title_len == title_len && memcmp(g->title, title, title_len) == 0) { + return true; } - g->close[ind].type = GROUPCHAT_CLOSE_CONNECTION; - g->close[ind].number = friendcon_id; - g->close[ind].closest = closest; - // 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); + memcpy(g->title, title, title_len); + g->title_len = title_len; + g->title_changed = true; + + if (peer_index >= 0) { + g->peers[peer_index].title_changed = true; + } - return ind; + return true; } /* Creates a new groupchat and puts it in the chats array. @@ -730,9 +1003,9 @@ static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t gr * return group number on success. * return -1 on failure. */ -int add_groupchat(Group_Chats *g_c, uint8_t type) +int add_groupchat(Group_Chats *g_c, uint8_t type, const uint8_t *uid) { - int groupnumber = create_group_chat(g_c); + int32_t groupnumber = create_group_chat(g_c); if (groupnumber == -1) { return -1; @@ -740,30 +1013,50 @@ 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; - new_symmetric_key(g->identifier + 1); + g->invite_called = true; + + if (uid != nullptr) { + memcpy(g->identifier + 1, uid, GROUP_IDENTIFIER_LENGTH - 1); + } else { + new_symmetric_key(g->identifier + 1); + } + g->identifier[0] = type; - 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); + int64_t peer_index = addpeer(g, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), -1); if (peer_index == -1) { return -1; } - setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, nullptr, false); + g->peers[peer_index].group_number = (uint16_t)groupnumber; - return groupnumber; + setnick(g, peer_index, g_c->m->name, g_c->m->name_length); + + return (int)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. - * return -1 if groupnumber is invalid. - */ -int del_groupchat(Group_Chats *g_c, uint32_t groupnumber) +static void on_offline(Group_c *g) +{ + g->join_mode = true; + g->fake_join = false; + g->auto_join = false; + g->need_send_name = false; + g->title_changed = false; + g->invite_called = false; + g->keep_leave = false; + g->disable_auto_join = false; + g->keep_join_index = -1; + + for (uint16_t i = 0; i < g->numjoinpeers; ++i) { + Group_Join_Peer *j = &g->joinpeers[i]; + j->unsubscribed = false; + j->online = false; + j->fails = 0; + } +} + +int enter_conference(Group_Chats *g_c, int32_t groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); @@ -771,90 +1064,196 @@ int del_groupchat(Group_Chats *g_c, uint32_t groupnumber) return -1; } - group_kill_peer_send(g_c, groupnumber, g->peer_number); + if (!g->disable_auto_join) { + return -2; + } + + on_offline(g); + return 0; +} + +static Group_c *disconnect_conference(const Group_Chats *g_c, int32_t groupnumber, UnsubscribeType u) +{ + if (u) { + conference_unsubscribe(g_c, groupnumber, u); + } + + Group_c *g = group_kill_peer_send(g_c, groupnumber); + + if (!g) { + return nullptr; + } + + memset(g->closest_peers, 0, sizeof(g->closest_peers)); + g->closest_peers_entry = 0; + + g->invite_called = false; - unsigned int i; + g->message_number = 0; + g->lossy_message_number = 0; + g->keep_join_index = -1; - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { + for (uint32_t i = 0; i < g->numpeers; ++i) { + Group_Peer *peer = &g->peers[i]; + + if (peer->friendcon_id == ALMOST_DELETED_PEER) { continue; } - g->close[i].type = GROUPCHAT_CLOSE_NONE; - kill_friend_connection(g_c->fr_c, g->close[i].number); - } + if (peer->friendcon_id >= 0) { + kill_friend_connection(g_c->fr_c, peer->friendcon_id); + } - for (i = 0; i < g->numpeers; ++i) { if (g->peer_on_leave) { - g->peer_on_leave(g->object, groupnumber, i, g->group[i].object); + g->peer_on_leave(g->object, groupnumber, peer->object); } + + free_peer_members(peer); } - free(g->group); + free(g->peers); + g->numpeers = 0; + g->peers = nullptr; + free(g->peers_list); + g->numpeers_in_list = 0; + g->peers_list = nullptr; if (g->group_on_delete) { g->group_on_delete(g->object, groupnumber); } - return wipe_group_chat(g_c, groupnumber); + g->object = nullptr; + g->peer_on_leave = nullptr; + g->peer_on_join = nullptr; + g->group_on_delete = nullptr; + + return g; +} + +int leave_conference(Group_Chats *g_c, int groupnumber, bool keep_leave) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + g->keep_leave = keep_leave; + + if (g->disable_auto_join) { + return -2; + } + + disconnect_conference(g_c, groupnumber, UNS_TEMP); + + g->dirty_list = true; + g->disable_auto_join = true; + g->keep_leave = keep_leave; + g->join_mode = false; + g->fake_join = false; + g->auto_join = false; + g->need_send_name = false; + g->title_changed = false; + g->invite_called = false; + + return 0; +} + +/* Delete a groupchat from the chats array. + * + * return 0 on success. + * return -1 if groupnumber is invalid. + */ +static int del_groupchat_internal(Group_Chats *g_c, int32_t groupnumber, UnsubscribeType uns) +{ + Group_c *g = disconnect_conference(g_c, groupnumber, uns); + + if (!g) { + return -1; + } + + free(g->joinpeers); + + crypto_memzero(&g_c->chats[groupnumber], sizeof(Group_c)); + + for (uint16_t i = groupnumber + 1; i < g_c->num_chats; ++i) { + if (g_c->chats[i].live) { + return 0; + } + } + + g_c->num_chats = (uint16_t)groupnumber; + realloc_conferences(g_c, groupnumber); + + return 0; +} + +int del_groupchat(Group_Chats *g_c, int groupnumber) +{ + return del_groupchat_internal(g_c, groupnumber, UNS_FOREVER); } -/* Copy the public key of peernumber who is in groupnumber to pk. +/* Copy the public key of peer_index who is in groupnumber to pk. * pk must be CRYPTO_PUBLIC_KEY_SIZE long. * * return 0 on success * return -1 if groupnumber is invalid. - * return -2 if peernumber is invalid. + * return -2 if peer_index is invalid. */ -int group_peer_pubkey(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, uint8_t *pk) +int group_peer_pubkey(const Group_Chats *g_c, int groupnumber, int peer_index, uint8_t *pk) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - if ((uint32_t)peernumber >= g->numpeers) { + if (peer_index < 0 || (uint32_t)peer_index >= g->numpeers_in_list) { + return -2; + } + + if (g->peers_list[peer_index] == INVALID_PEER_INDEX) { return -2; } - memcpy(pk, g->group[peernumber].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(pk, g->peers[g->peers_list[peer_index]].real_pk, CRYPTO_PUBLIC_KEY_SIZE); return 0; } /* - * Return the size of peernumber's name. + * Return the size of peer_index's name. * * return -1 if groupnumber is invalid. - * return -2 if peernumber is invalid. + * return -2 if peer_index is invalid. */ -int group_peername_size(const Group_Chats *g_c, uint32_t groupnumber, int peernumber) +int group_peername_size(const Group_Chats *g_c, int groupnumber, int peer_index) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - if ((uint32_t)peernumber >= g->numpeers) { + if (peer_index < 0 || (uint32_t)peer_index >= g->numpeers_in_list) { return -2; } - if (g->group[peernumber].nick_len == 0) { - return 0; + if (g->peers_list[peer_index] == INVALID_PEER_INDEX) { + return -2; } - return g->group[peernumber].nick_len; + return g->peers[g->peers_list[peer_index]].nick_len; } -/* Copy the name of peernumber who is in groupnumber to name. + +/* Copy the name of peer_index who is in groupnumber to name. * name must be at least MAX_NAME_LENGTH long. * * return length of name if success * return -1 if groupnumber is invalid. - * return -2 if peernumber is invalid. + * return -2 if peer_index is invalid. */ -int group_peername(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, uint8_t *name) +int group_peername(const Group_Chats *g_c, int groupnumber, int peer_index, uint8_t *name) { Group_c *g = get_group_c(g_c, groupnumber); @@ -862,16 +1261,20 @@ int group_peername(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, return -1; } - if ((uint32_t)peernumber >= g->numpeers) { + if (peer_index < 0 || (uint32_t)peer_index >= g->numpeers_in_list) { return -2; } - if (g->group[peernumber].nick_len == 0) { - return 0; + if (g->peers_list[peer_index] == INVALID_PEER_INDEX) { + return -2; } - memcpy(name, g->group[peernumber].nick, g->group[peernumber].nick_len); - return g->group[peernumber].nick_len; + Group_Peer *peer = &g->peers[g->peers_list[peer_index]]; + + peer->nick_changed = false; + + memcpy(name, peer->nick, peer->nick_len); + return peer->nick_len; } /* List all the peers in the group chat. @@ -884,19 +1287,26 @@ int group_peername(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, * * return -1 on failure. */ -int group_names(const Group_Chats *g_c, uint32_t groupnumber, uint8_t names[][MAX_NAME_LENGTH], uint16_t lengths[], +int group_names(const Group_Chats *g_c, int groupnumber, uint8_t names[][MAX_NAME_LENGTH], uint16_t lengths[], uint16_t length) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - unsigned int i; + uint32_t i; + + for (i = 0; i < g->numpeers_in_list && i < length; ++i) { + int l = group_peername(g_c, groupnumber, i, names[i]); - for (i = 0; i < g->numpeers && i < length; ++i) { - lengths[i] = group_peername(g_c, groupnumber, i, names[i]); + if (l >= 0) { + lengths[i] = l; + } else { + lengths[i] = 0; + names[i][0] = 0; + } } return i; @@ -905,40 +1315,41 @@ int group_names(const Group_Chats *g_c, uint32_t groupnumber, uint8_t names[][MA /* Return the number of peers in the group chat on success. * return -1 if groupnumber is invalid. */ -int group_number_peers(const Group_Chats *g_c, uint32_t groupnumber) +int group_number_peers(const Group_Chats *g_c, int groupnumber) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - return g->numpeers; + return g->numpeers_in_list; } -/* return 1 if the peernumber corresponds to ours. - * return 0 if the peernumber is not ours. +/* return 1 if the peer_index corresponds to ours. + * return 0 if the peer_index is not ours. * return -1 if groupnumber is invalid. - * return -2 if peernumber is invalid. + * return -2 if peer_index is invalid. * return -3 if we are not connected to the group chat. */ -int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, int peernumber) +int group_peer_index_is_ours(const Group_Chats *g_c, int groupnumber, int peer_index) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - if ((uint32_t)peernumber >= g->numpeers) { + if ((unsigned)peer_index >= g->numpeers_in_list) { return -2; } - if (g->status != GROUPCHAT_STATUS_CONNECTED) { + if (!g->live) { return -3; } - return g->peer_number == g->group[peernumber].peer_number; + const Group_Peer *peer = &g->peers[g->peers_list[peer_index]]; + return id_equal(g->real_pk, peer->real_pk); } /* return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. @@ -946,9 +1357,9 @@ int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, int p * return -1 on failure. * return type on success. */ -int group_get_type(const Group_Chats *g_c, uint32_t groupnumber) +int group_get_type(const Group_Chats *g_c, int groupnumber) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; @@ -957,63 +1368,81 @@ int group_get_type(const Group_Chats *g_c, uint32_t groupnumber) return g->identifier[0]; } +/* Copies the unique id of group_chat[groupnumber] into uid. +* +* return false on failure. +* return true on success. +*/ +bool conference_get_id(const Group_Chats *g_c, int groupnumber, uint8_t *uid) +{ + const Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return false; + } + + if (uid == nullptr) { + memcpy(uid, g->identifier + 1, sizeof(g->identifier) - 1); + } + + return true; +} + /* Send a group packet to friendcon_id. * - * return 1 on success - * return 0 on failure + * return true on success + * return false on failure */ -static unsigned int send_packet_group_peer(Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, - uint16_t group_num, const uint8_t *data, uint16_t length) +static bool send_packet_group_peer(Friend_Connections *fr_c, uint32_t friendcon_id, uint8_t packet_id, + uint16_t other_group_num, const uint8_t *data, uint16_t length) { - if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) { - return 0; + uint16_t plen = 1 + sizeof(uint16_t) + length; + + if (plen > MAX_CRYPTO_DATA_SIZE) { + return false; } - group_num = net_htons(group_num); - VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length); + other_group_num = net_htons(other_group_num); + uint8_t packet[MAX_CRYPTO_DATA_SIZE]; packet[0] = packet_id; - memcpy(packet + 1, &group_num, sizeof(uint16_t)); + memcpy(packet + 1, &other_group_num, sizeof(uint16_t)); memcpy(packet + 1 + sizeof(uint16_t), data, length); - return write_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, - SIZEOF_VLA(packet), 0) != -1; + return write_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, (int)friendcon_id), + packet, plen, 0) != -1; } /* Send a group lossy packet to friendcon_id. * - * return 1 on success - * return 0 on failure + * return true on success + * return false on failure */ -static unsigned int send_lossy_group_peer(Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, - uint16_t group_num, const uint8_t *data, uint16_t length) +static bool send_lossy_group_peer(Friend_Connections *fr_c, uint32_t friendcon_id, uint8_t packet_id, + uint16_t group_num, const uint8_t *data, uint16_t length) { - if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) { - return 0; + uint16_t plen = 1 + sizeof(uint16_t) + length; + + if (plen > MAX_CRYPTO_DATA_SIZE) { + return false; } group_num = net_htons(group_num); - VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length); + uint8_t packet[MAX_CRYPTO_DATA_SIZE]; packet[0] = packet_id; memcpy(packet + 1, &group_num, sizeof(uint16_t)); memcpy(packet + 1 + sizeof(uint16_t), data, length); - return send_lossy_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), - packet, SIZEOF_VLA(packet)) != -1; + return send_lossy_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, + (int)friendcon_id), packet, plen) != -1; } -#define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) -#define INVITE_ID 0 - -#define INVITE_RESPONSE_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + GROUP_IDENTIFIER_LENGTH) -#define INVITE_RESPONSE_ID 1 - /* invite friendnumber to groupnumber. * * return 0 on success. * return -1 if groupnumber is invalid. * return -2 if invite packet failed to send. */ -int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) +int invite_friend(Group_Chats *g_c, int32_t friendnumber, int groupnumber) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; @@ -1029,12 +1458,9 @@ int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) return 0; } - wipe_group_chat(g_c, groupnumber); return -2; } -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.) * * expected_type is the groupchat type we expect the chat we are joining is. @@ -1047,8 +1473,21 @@ static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t * return -5 if group instance failed to initialize. * return -6 if join packet fails to send. */ -int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length) +int join_groupchat(Group_Chats *g_c, int32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length) { + if (length == GROUP_IDENTIFIER_LENGTH - 1) { + int groupnumber = conference_by_uid(g_c, data); + + if (groupnumber != -1) { + Group_c *g = &g_c->chats[groupnumber]; + g->invite_called |= g->fake_join; + return groupnumber; + } + + /* create groupchat with exist id */ + return add_groupchat(g_c, expected_type, data); + } + if (length != sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) { return -1; } @@ -1057,52 +1496,103 @@ 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); - - if (friendcon_id == -1) { - return -3; - } + bool just_created = false; + int32_t groupnumber = get_group_num(g_c, data + sizeof(uint16_t)); - if (get_group_num(g_c, data + sizeof(uint16_t)) != -1) { - return -4; + if (groupnumber == -1) { + groupnumber = create_group_chat(g_c); + just_created = true; } - int groupnumber = create_group_chat(g_c); - if (groupnumber == -1) { return -5; } Group_c *g = &g_c->chats[groupnumber]; - uint16_t group_num = net_htons(groupnumber); - g->status = GROUPCHAT_STATUS_VALID; - g->number_joined = -1; + if (g->fake_join) { + g->need_send_name = true; + g->invite_called = true; + return (int)groupnumber; + } + + int friendcon_id = getfriendcon_id(g_c->m, friendnumber); + + if (friendcon_id == -1) { + return -3; + } + memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); + g->dirty_list = true; + g->need_send_name = true; + + int64_t peer_index = addpeer(g, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), + g->auto_join ? get_self_peer_gid(g) : -1); + + if (peer_index != -1) { + setnick(g, peer_index, g_c->m->name, g_c->m->name_length); + g->peers[peer_index].group_number = (uint16_t)groupnumber; + } + uint8_t response[INVITE_RESPONSE_PACKET_SIZE]; response[0] = INVITE_RESPONSE_ID; + uint16_t group_num = htons((uint16_t)groupnumber); memcpy(response + 1, &group_num, sizeof(uint16_t)); memcpy(response + 1 + sizeof(uint16_t), data, sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH); if (send_conference_invite_packet(g_c->m, friendnumber, response, sizeof(response))) { - uint16_t other_groupnum; + if (just_created) { + memcpy(g->identifier, data + sizeof(uint16_t), GROUP_IDENTIFIER_LENGTH); + g->invite_called = true; /* just created means client called join_groupchat */ + } + + uint16_t other_groupnum = 0; memcpy(&other_groupnum, data, sizeof(other_groupnum)); other_groupnum = net_ntohs(other_groupnum); - memcpy(g->identifier, data + sizeof(uint16_t), GROUP_IDENTIFIER_LENGTH); - int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnumber, 0, 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; + 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); + peer_index = addpeer(g, groupnumber, real_pk, temp_pk, -1); + + if (g->peers[peer_index].friendcon_id != friendcon_id) { + if (g->peers[peer_index].friendcon_id >= 0) { + kill_friend_connection(g_c->fr_c, g->peers[peer_index].friendcon_id); + g->peers[peer_index].friendcon_id = -1; + g->peers[peer_index].connected = false; + } + } + + if (g->auto_join) { + g->peers[peer_index].auto_join = true; + 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); + + if (g->peers[peer_index].friendcon_id < 0) { + g->peers[peer_index].friendcon_id = friendcon_id; + g->peers[peer_index].connected = true; + friend_connection_lock(g_c->fr_c, friendcon_id); + } + + g->peers[peer_index].group_number = INVALID_GROUP_NUMBER; + g->peers[peer_index].keep_connection = 2; + } else { + g->peers[peer_index].group_number = other_groupnum; } send_peer_query(g_c, friendcon_id, other_groupnum); - return groupnumber; + + if (!just_created) { + return -4; + } + + return (int)groupnumber; + } + + if (just_created) { + g->live = false; } - g->status = GROUPCHAT_STATUS_NONE; return -6; } @@ -1110,18 +1600,23 @@ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_typ * * NOTE: Handler must return 0 if packet is to be relayed, -1 if the packet should not be relayed. * - * Function(void *group object (set with group_set_object), uint32_t groupnumber, uint32_t friendgroupnumber, void *group peer object (set with group_peer_set_object), const uint8_t *packet, uint16_t length) + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object), const uint8_t *packet, uint16_t length) */ -void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*function)(void *, uint32_t, uint32_t, - void *, +void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*function)(void *, int, int, void *, const uint8_t *, uint16_t)) { +#if 0 g_c->lossy_packethandlers[byte].function = function; +#endif + + if (192 /* GROUP_AUDIO_PACKET_ID */ == byte) { + g_c->lossy_packethandler = function; + } } /* Set the callback for group invites. * - * Function(Group_Chats *g_c, int32_t friendnumber, uint8_t type, uint8_t *data, size_t length, void *userdata) + * Function(Group_Chats *g_c, int32_t friendnumber, uint8_t type, uint8_t *data, uint16_t length, void *userdata) * * data of length is what needs to be passed to join_groupchat(). */ @@ -1131,10 +1626,9 @@ void g_callback_group_invite(Group_Chats *g_c, void (*function)(Messenger *m, ui g_c->invite_callback = function; } -// TODO(sudden6): function signatures in comments are incorrect /* Set the callback for group messages. * - * Function(Group_Chats *g_c, uint32_t groupnumber, uint32_t friendgroupnumber, uint8_t * message, size_t length, void *userdata) + * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) */ void g_callback_group_message(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, uint32_t, int, const uint8_t *, size_t, void *)) @@ -1163,7 +1657,6 @@ void g_callback_peer_list_changed(Group_Chats *g_c, void (*function)(Messenger * g_c->peer_list_changed_callback = function; } -// TODO(sudden6): function signatures are incorrect /* Set callback function for title changes. * * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * title, uint8_t length, void *userdata) @@ -1177,13 +1670,12 @@ void g_callback_group_title(Group_Chats *g_c, void (*function)(Messenger *m, uin /* Set a function to be called when a new peer joins a group chat. * - * Function(void *group object (set with group_set_object), uint32_t groupnumber, uint32_t friendgroupnumber) + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber) * * return 0 on success. * return -1 on failure. */ -int callback_groupchat_peer_new(const Group_Chats *g_c, uint32_t groupnumber, void (*function)(void *, uint32_t, - uint32_t)) +int callback_groupchat_peer_new(const Group_Chats *g_c, int groupnumber, void (*function)(void *, int32_t, int)) { Group_c *g = get_group_c(g_c, groupnumber); @@ -1191,19 +1683,25 @@ int callback_groupchat_peer_new(const Group_Chats *g_c, uint32_t groupnumber, vo return -1; } - g->peer_on_join = function; + if (g->peer_on_join != function) { + g->peer_on_join = function; + + for (uint32_t i = 0; i < g->numpeers; ++i) { + g->peer_on_join(g->object, groupnumber, i); + } + } + return 0; } /* Set a function to be called when a peer leaves a group chat. * - * Function(void *group object (set with group_set_object), uint32_t groupnumber, uint32_t friendgroupnumber, void *group peer object (set with group_peer_set_object)) + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object)) * * return 0 on success. * return -1 on failure. */ -int callback_groupchat_peer_delete(Group_Chats *g_c, uint32_t groupnumber, void (*function)(void *, uint32_t, uint32_t, - void *)) +int callback_groupchat_peer_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int32_t, void *)) { Group_c *g = get_group_c(g_c, groupnumber); @@ -1222,7 +1720,7 @@ int callback_groupchat_peer_delete(Group_Chats *g_c, uint32_t groupnumber, void * return 0 on success. * return -1 on failure. */ -int callback_groupchat_delete(Group_Chats *g_c, uint32_t groupnumber, void (*function)(void *, uint32_t)) +int callback_groupchat_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int32_t)) { Group_c *g = get_group_c(g_c, groupnumber); @@ -1234,11 +1732,7 @@ int callback_groupchat_delete(Group_Chats *g_c, uint32_t groupnumber, void (*fun return 0; } -static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint8_t message_id, const uint8_t *data, - uint16_t len); - -#define GROUP_MESSAGE_PING_ID 0 -static int group_ping_send(const Group_Chats *g_c, uint32_t groupnumber) +static int group_ping_send(const Group_Chats *g_c, int groupnumber) { if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_PING_ID, nullptr, 0) > 0) { return 0; @@ -1247,70 +1741,191 @@ static int group_ping_send(const Group_Chats *g_c, uint32_t groupnumber) return -1; } -#define GROUP_MESSAGE_NEW_PEER_ID 16 -#define GROUP_MESSAGE_NEW_PEER_LENGTH (sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2) +static int nick_request_send(const Group_Chats *g_c, int groupnumber, int gid) +{ + uint8_t d[sizeof(uint16_t)]; + net_pack_u16(d, gid); + + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NICKNAME_ID, d, sizeof(d)) > 0) { + return 0; + } + + return -1; +} + + +static Group_Peer *get_self_peer(Group_c *g) +{ + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (id_equal(g->real_pk, g->peers[i].real_pk)) { + return &g->peers[i]; + } + } + + return nullptr; +} + + +static int get_self_peer_gid(const Group_c *g) +{ + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (id_equal(g->real_pk, g->peers[i].real_pk)) { + return g->peers[i].gid; + } + } + + return -1; +} + +static void change_self_peer_gid(Group_Chats *g_c, int32_t groupnumber, int self_peer_gid) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (id_equal(g->peers[i].real_pk, g->real_pk)) { + if (g->peers[i].gid != self_peer_gid) { + g->dirty_list = true; + g->peers[i].gid = self_peer_gid; + } + + return; + } + } + + /* no self peer found! Add it */ + + int64_t peer_index = addpeer(g, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), self_peer_gid); + + if (peer_index == -1) { + return; + } + + setnick(g, peer_index, g_c->m->name, g_c->m->name_length); +} + /* send a new_peer message * return 0 on success * return -1 on failure */ -static int group_new_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num, const uint8_t *real_pk, - uint8_t *temp_pk) +static int group_new_peer_send(Group_Chats *g_c, int32_t groupnumber, uint16_t peer_gid, const uint8_t *real_pk, + const uint8_t *temp_pk) { uint8_t packet[GROUP_MESSAGE_NEW_PEER_LENGTH]; - - peer_num = net_htons(peer_num); - memcpy(packet, &peer_num, sizeof(uint16_t)); + peer_gid = net_htons(peer_gid); + memcpy(packet, &peer_gid, sizeof(uint16_t)); memcpy(packet + sizeof(uint16_t), real_pk, CRYPTO_PUBLIC_KEY_SIZE); memcpy(packet + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); - if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NEW_PEER_ID, packet, sizeof(packet)) > 0) { + /* make self gid valid due self is inviter */ + const Group_c *g = g_c->chats + groupnumber; + + if (get_self_peer_gid(g) < 0) { + change_self_peer_gid(g_c, groupnumber, find_new_peer_gid(g)); + } + + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NEW_PEER_ID, packet, GROUP_MESSAGE_NEW_PEER_LENGTH) > 0) { return 0; } return -1; } -#define GROUP_MESSAGE_KILL_PEER_ID 17 -#define GROUP_MESSAGE_KILL_PEER_LENGTH (sizeof(uint16_t)) - -/* send a kill_peer message - * return 0 on success - * return -1 on failure - */ -static int group_kill_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num) +static int conference_unsubscribe(const Group_Chats *g_c, int32_t groupnumber, UnsubscribeType u) { - uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH]; + const Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } - peer_num = net_htons(peer_num); - memcpy(packet, &peer_num, sizeof(uint16_t)); + uint8_t packet[GROUP_IDENTIFIER_LENGTH + CRYPTO_PUBLIC_KEY_SIZE]; + packet[0] = (uint8_t)u; + memcpy(packet + 1, g->identifier + 1, GROUP_IDENTIFIER_LENGTH - 1); + memcpy(packet + GROUP_IDENTIFIER_LENGTH, g->real_pk, CRYPTO_PUBLIC_KEY_SIZE); - if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_KILL_PEER_ID, packet, sizeof(packet)) > 0) { + if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_UNSUBSCRIBE_ID, packet, sizeof(packet)) > 0) { return 0; } return -1; } -#define GROUP_MESSAGE_NAME_ID 48 -/* send a name message - * return 0 on success - * return -1 on failure - */ -static int group_name_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *nick, uint16_t nick_len) +static void send_peer_nums(const Group_Chats *g_c, int32_t groupnumber, int friendcon_id, uint16_t other_group_num) { - if (nick_len > MAX_NAME_LENGTH) { - return -1; + const Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; } - if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NAME_ID, nick, nick_len) > 0) { - return 0; + uint8_t packet[MAX_CRYPTO_DATA_SIZE - (1 + sizeof(uint16_t))]; + packet[0] = PEER_GROUP_NUM_ID; + size_t ptr = 1; + + for (uint32_t i = 0; i < g->numpeers; ++i) { + const Group_Peer *peer = &g->peers[i]; + + if (peer->group_number == INVALID_GROUP_NUMBER) { + continue; + } + + if (ptr + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint16_t) > sizeof(packet)) { + send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, other_group_num, packet, ptr); + ptr = 1; + } + + memcpy(packet + ptr, peer->real_pk, CRYPTO_PUBLIC_KEY_SIZE); + ptr += CRYPTO_PUBLIC_KEY_SIZE; + + ptr += net_pack_u16(packet + ptr, peer->group_number); } - return -1; + if (ptr > 1) { + send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, other_group_num, packet, ptr); + } +} + +static Group_c *group_kill_peer_send(const Group_Chats *g_c, int32_t groupnumber) +{ + Group_c *g = get_group_c(g_c, groupnumber); + + if (g) { + uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH]; + + int self_gid = get_self_peer_gid(g); + + if (self_gid < 0) { + return g; + } + + uint16_t peer_gid = htons((uint16_t)self_gid); + memcpy(packet, &peer_gid, sizeof(uint16_t)); + send_message_group(g_c, groupnumber, GROUP_MESSAGE_KILL_PEER_ID, packet, sizeof(packet)); + } + + return g; } -#define GROUP_MESSAGE_TITLE_ID 49 +static void group_name_send(const Group_Chats *g_c, int32_t groupnumber, const uint8_t *nick, uint8_t nick_len) +{ + if (nick_len > MAX_NAME_LENGTH) { + return; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + g->need_send_name = false; + send_message_group(g_c, groupnumber, GROUP_MESSAGE_NAME_ID, nick, nick_len); +} /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success @@ -1318,7 +1933,7 @@ static int group_name_send(const Group_Chats *g_c, uint32_t groupnumber, const u * return -2 if title is too long or empty. * return -3 if packet fails to send. */ -int group_title_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *title, uint8_t title_len) +int group_title_send(const Group_Chats *g_c, int groupnumber, const uint8_t *title, uint8_t title_len) { Group_c *g = get_group_c(g_c, groupnumber); @@ -1353,9 +1968,9 @@ int group_title_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t * return -1 of groupnumber is invalid. * return -2 if title is too long or empty. */ -int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber) +int group_title_get_size(const Group_Chats *g_c, int groupnumber) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; @@ -1375,9 +1990,9 @@ int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber) * return -1 if groupnumber is invalid. * return -2 if title is too long or empty. */ -int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title) +int group_title_get(const Group_Chats *g_c, int groupnumber, uint8_t *title) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; @@ -1391,6 +2006,57 @@ int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title return g->title_len; } +static void unsubscribe_peer(Group_Chats *g_c, const uint8_t *conf_id, const uint8_t *peer_pk, UnsubscribeType u) +{ + Group_c *g = get_group_c(g_c, conference_by_uid(g_c, conf_id)); + + if (g == nullptr) { + return; + } + + for (uint16_t i = 0; i < g->numjoinpeers; ++i) { + Group_Join_Peer *jp = &g->joinpeers[i]; + + if (!id_equal(jp->real_pk, peer_pk)) { + continue; + } + + if (UNS_FOREVER == u) { + --g->numjoinpeers; + + if (g->numjoinpeers > 0) { + memcpy(jp, g->joinpeers + g->numjoinpeers, sizeof(Group_Join_Peer)); + } else { + free(g->joinpeers); + g->joinpeers = nullptr; + } + } else if (UNS_TEMP == u) { + jp->unsubscribed = true; + } + + break; + } +} + +static void set_peer_groupnum(Group_c *g, const uint8_t *peer_pk, uint16_t gn) +{ + if (id_equal(g->real_pk, peer_pk)) { + /* ignore */ + return; + } + + int64_t i = peer_in_chat(g, peer_pk); + + if (i >= 0) { + Group_Peer *peer = &g->peers[i]; + + if (peer->connected && peer->group_number == INVALID_GROUP_NUMBER) { + peer->group_number = gn; + } + } +} + + static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *userdata) { @@ -1404,22 +2070,38 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con uint16_t invite_length = length - 1; switch (data[0]) { + case INVITE_MYGROUP_ID: case INVITE_ID: { - if (length != INVITE_PACKET_SIZE) { + + if (length < INVITE_PACKET_SIZE) { return; } - int groupnumber = get_group_num(g_c, data + 1 + sizeof(uint16_t)); + int32_t groupnumber = get_group_num(g_c, data + 1 + sizeof(uint16_t)); if (groupnumber == -1) { + if (data[0] == INVITE_MYGROUP_ID) { + return; + } + if (g_c->invite_callback) { - g_c->invite_callback(m, friendnumber, *(invite_data + sizeof(uint16_t)), invite_data, invite_length, userdata); + g_c->invite_callback(m, friendnumber, invite_data[sizeof(uint16_t)], + invite_data, invite_length, userdata); } + } else { + Group_c *g = get_group_c(g_c, groupnumber); - return; + if (g->keep_leave) { + return; + } + + g->join_mode = false; + g->disable_auto_join = false; + g->invite_called = false; + join_groupchat(g_c, friendnumber, invite_data[sizeof(uint16_t)], invite_data, invite_length); } - break; + return; } case INVITE_RESPONSE_ID: { @@ -1427,287 +2109,259 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con return; } - uint16_t other_groupnum, groupnum; - memcpy(&groupnum, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); - groupnum = net_ntohs(groupnum); + uint16_t groupnum_in; + memcpy(&groupnum_in, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); + groupnum_in = net_ntohs(groupnum_in); - Group_c *g = get_group_c(g_c, groupnum); + const uint8_t *conference_id = data + 2 + sizeof(uint16_t) * 2; + + /* we absolutely do not care about groupnumber in packet + * but, in case groupnum_in != groupnumber, + * we should send INVITE_ID packet back to peer with correct groupnumber */ + + const int32_t groupnumber = get_group_num(g_c, data + 1 + sizeof(uint16_t) * 2); + Group_c *g = get_group_c(g_c, groupnumber); if (!g) { + uint8_t nosuchgroup[GROUP_IDENTIFIER_LENGTH + CRYPTO_PUBLIC_KEY_SIZE]; + nosuchgroup[0] = INVITE_UNSUBSCRIBE_ID; + memcpy(nosuchgroup + 1, conference_id, GROUP_IDENTIFIER_LENGTH - 1); + memcpy(nosuchgroup + GROUP_IDENTIFIER_LENGTH, nc_get_self_public_key(g_c->m->net_crypto), + CRYPTO_PUBLIC_KEY_SIZE); + send_conference_invite_packet(g_c->m, friendnumber, nosuchgroup, sizeof(nosuchgroup)); return; } - if (crypto_memcmp(data + 1 + sizeof(uint16_t) * 2, g->identifier, GROUP_IDENTIFIER_LENGTH) != 0) { + if (g->numpeers >= PEER_INDEX_MAX || g->keep_leave) { return; } - /* TODO(irungentoo): what if two people enter the group at the same time and - are given the same peer_number by different nodes? */ - uint16_t peer_number = rand(); - - unsigned int tries = 0; + if (groupnumber != groupnum_in) { + /* send INVITE_ID to restore valid groupnum + * old toxcore will ignore this packet, + */ - while (get_peer_index(g, peer_number) != -1) { - peer_number = rand(); - ++tries; + uint8_t invite[INVITE_PACKET_SIZE]; + invite[0] = INVITE_MYGROUP_ID; + uint16_t groupchat_num = htons((uint16_t)groupnumber); + memcpy(invite + 1, &groupchat_num, sizeof(groupchat_num)); + memcpy(invite + 1 + sizeof(groupchat_num), g->identifier, GROUP_IDENTIFIER_LENGTH); - if (tries > 32) { - return; - } + send_conference_invite_packet(g_c->m, friendnumber, invite, sizeof(invite)); + return; } - memcpy(&other_groupnum, data + 1, sizeof(uint16_t)); - other_groupnum = net_ntohs(other_groupnum); - int friendcon_id = getfriendcon_id(m, friendnumber); 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); + /* peer_gid collision will be resolved */ + uint16_t peer_gid = find_new_peer_gid(g); - if (close_index != -1) { - g->close[close_index].group_number = other_groupnum; - g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; - } + int64_t peer_index = addpeer(g, groupnumber, real_pk, temp_pk, peer_gid); - group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); - break; - } + if (g->peers[peer_index].friendcon_id != friendcon_id) { + if (g->peers[peer_index].friendcon_id >= 0) { + kill_friend_connection(g_c->fr_c, g->peers[peer_index].friendcon_id); + g->peers[peer_index].friendcon_id = -1; + } - default: - return; - } -} + g->peers[peer_index].friendcon_id = friendcon_id; + g->peers[peer_index].connected = true; + friend_connection_lock(g_c->fr_c, friendcon_id); + } -/* Find index of friend in the close list; - * - * returns index on success - * returns -1 on failure. - */ -static int friend_in_close(Group_c *g, int friendcon_id) -{ - unsigned int i; + 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); - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { - continue; - } + g->peers[peer_index].group_number = net_ntohs(*(const uint16_t *)(data + 1)); - if (g->close[i].number != (uint32_t)friendcon_id) { - continue; - } + g->need_send_name = true; + group_new_peer_send(g_c, groupnumber, peer_gid, real_pk, temp_pk); - return i; - } + g->join_mode = false; + g->keep_leave = false; + g->disable_auto_join = false; + break; + } - return -1; -} + case INVITE_UNSUBSCRIBE_ID: { + if (length < GROUP_IDENTIFIER_LENGTH + CRYPTO_PUBLIC_KEY_SIZE) { + return; + } -/* return number of connected close connections. - */ -static unsigned int count_close_connected(Group_c *g) -{ - unsigned int i, count = 0; + unsubscribe_peer(g_c, data + 1, data + GROUP_IDENTIFIER_LENGTH, UNS_FOREVER); + break; + } - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type == GROUPCHAT_CLOSE_ONLINE) { - ++count; + default: { + return; } } - - return count; } -#define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) - -static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t *identifier) +static int64_t send_packet_online(Friend_Connections *fr_c, int friendcon_id, + uint16_t my_group_num, const uint8_t *identifier) { uint8_t packet[1 + ONLINE_PACKET_DATA_SIZE]; - group_num = net_htons(group_num); + my_group_num = net_htons(my_group_num); packet[0] = PACKET_ID_ONLINE_PACKET; - memcpy(packet + 1, &group_num, sizeof(uint16_t)); + memcpy(packet + 1, &my_group_num, sizeof(uint16_t)); memcpy(packet + 1 + sizeof(uint16_t), identifier, GROUP_IDENTIFIER_LENGTH); return write_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, sizeof(packet), 0) != -1; } -static unsigned int send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t group_num); - static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length) { - if (length != ONLINE_PACKET_DATA_SIZE) { + if (length < ONLINE_PACKET_DATA_SIZE) { return -1; } int groupnumber = get_group_num(g_c, data + sizeof(uint16_t)); - - if (groupnumber == -1) { - return -1; - } - uint16_t other_groupnum; memcpy(&other_groupnum, data, sizeof(uint16_t)); other_groupnum = net_ntohs(other_groupnum); - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - int index = friend_in_close(g, friendcon_id); + uint32_t peer_index = -1; - if (index == -1) { - return -1; + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->peers[i].friendcon_id == friendcon_id) { + peer_index = i; + break; + } } - if (g->close[index].type == GROUPCHAT_CLOSE_ONLINE) { + if (peer_index == -1) { return -1; } - if (count_close_connected(g) == 0) { + Group_Peer *peer = &g->peers[peer_index]; + + if (peer->group_number != other_groupnum) { + peer->group_number = other_groupnum; send_peer_query(g_c, friendcon_id, other_groupnum); } - g->close[index].group_number = other_groupnum; - g->close[index].type = GROUPCHAT_CLOSE_ONLINE; - send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); + bool in_close = false; - if (g->number_joined != -1 && count_close_connected(g) >= DESIRED_CLOSE_CONNECTIONS) { - int fr_close_index = friend_in_close(g, g->number_joined); + for (uint32_t i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + if (closest(g, i)) { + Group_Peer *peer_from = &g->peers[g->closest_peers[i]]; - if (fr_close_index == -1) { - return -1; - } + if (peer_from->friendcon_id == friendcon_id) { + peer_from->need_send_peers = true; + } - if (!g->close[fr_close_index].closest) { - 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); - g->number_joined = -1; + if (g->closest_peers[i] == peer_index) { + in_close = true; + } } } - return 0; -} + if (!in_close) { + send_packet_online(g_c->fr_c, friendcon_id, (uint16_t)groupnumber, g->identifier); + } -#define PEER_KILL_ID 1 -#define PEER_QUERY_ID 8 -#define PEER_RESPONSE_ID 9 -#define PEER_TITLE_ID 10 -// we could send title with invite, but then if it changes between sending and accepting inv, joinee won't see it + return 0; +} -/* 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 void send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t other_group_num) { uint8_t packet[1]; packet[0] = PEER_KILL_ID; - return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); + send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, other_group_num, packet, sizeof(packet)); } -/* return 1 on success. - * return 0 on failure - */ -static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num) +static void send_peer_query(const Group_Chats *g_c, int friendcon_id, uint16_t other_group_num) { uint8_t packet[1]; packet[0] = PEER_QUERY_ID; - return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); + send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, other_group_num, packet, sizeof(packet)); } -/* return number of peers sent on success. - * return 0 on failure. - */ -static unsigned int send_peers(Group_Chats *g_c, uint32_t groupnumber, int friendcon_id, uint16_t group_num) +static void send_peers(Group_Chats *g_c, int32_t groupnumber, int friendcon_id, uint16_t other_group_num) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { - return 0; + return; } uint8_t packet[MAX_CRYPTO_DATA_SIZE - (1 + sizeof(uint16_t))]; packet[0] = PEER_RESPONSE_ID; uint8_t *p = packet + 1; - uint16_t sent = 0; - unsigned int i; + uint32_t sent = 0; + uint32_t i = 0; for (i = 0; i < g->numpeers; ++i) { - if ((p - packet) + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1 + g->group[i].nick_len > sizeof(packet)) { - if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, (p - packet))) { + if (g->peers[i].gid < 0) { + continue; + } + + if ((p - packet) + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1 + g->peers[i].nick_len > sizeof(packet)) { + if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, other_group_num, packet, + p - packet)) { sent = i; } else { - return sent; + return; } p = packet + 1; } - 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); + uint16_t peer_gid = net_htons(g->peers[i].gid); + memcpy(p, &peer_gid, sizeof(peer_gid)); + p += sizeof(peer_gid); + memcpy(p, g->peers[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE); p += CRYPTO_PUBLIC_KEY_SIZE; - memcpy(p, g->group[i].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(p, g->peers[i].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); p += CRYPTO_PUBLIC_KEY_SIZE; - *p = g->group[i].nick_len; + *p = g->peers[i].nick_len; p += 1; - memcpy(p, g->group[i].nick, g->group[i].nick_len); - p += g->group[i].nick_len; + memcpy(p, g->peers[i].nick, g->peers[i].nick_len); + p += g->peers[i].nick_len; } if (sent != i) { - if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, (p - packet))) { - sent = i; - } + send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, other_group_num, packet, + p - packet); } if (g->title_len) { - VLA(uint8_t, Packet, 1 + g->title_len); - Packet[0] = PEER_TITLE_ID; - memcpy(Packet + 1, g->title, g->title_len); - send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, Packet, SIZEOF_VLA(Packet)); + uint8_t title_packet[1 + MAX_NAME_LENGTH]; + title_packet[0] = PEER_TITLE_ID; + memcpy(title_packet + 1, g->title, g->title_len); + send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, other_group_num, + title_packet, 1 + g->title_len); } - - return sent; } -static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, - void *userdata) +static void accept_peers_list(Group_c *g, int32_t groupnumber, const uint8_t *data, uint16_t length) { if (length == 0) { - return -1; - } - - Group_c *g = get_group_c(g_c, groupnumber); - - if (!g) { - return -1; + return; } const uint8_t *d = data; - while ((unsigned int)(length - (d - data)) >= sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1) { - uint16_t peer_num; - memcpy(&peer_num, d, sizeof(peer_num)); - peer_num = net_ntohs(peer_num); + while ((unsigned)(length - (d - data)) >= sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1) { + uint16_t peer_gid; + memcpy(&peer_gid, d, sizeof(peer_gid)); + peer_gid = net_ntohs(peer_gid); d += sizeof(uint16_t); - int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, true); + int64_t peer_index = addpeer(g, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_gid); if (peer_index == -1) { - return -1; - } - - if (g->status == GROUPCHAT_STATUS_VALID - && public_key_cmp(d, nc_get_self_public_key(g_c->m->net_crypto)) == 0) { - g->peer_number = peer_num; - g->status = GROUPCHAT_STATUS_CONNECTED; - group_name_send(g_c, groupnumber, g_c->m->name, g_c->m->name_length); + return; } d += CRYPTO_PUBLIC_KEY_SIZE * 2; @@ -1715,18 +2369,42 @@ static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8 d += 1; if (name_length > (length - (d - data)) || name_length > MAX_NAME_LENGTH) { - return -1; + return; } - setnick(g_c, groupnumber, peer_index, d, name_length, userdata, true); + setnick(g, peer_index, d, name_length); d += name_length; } +} + +static bool self_peer_gid_collision(Group_c *g) +{ + uint32_t me = ~0; + int my_gid = 0; + + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (id_equal(g->real_pk, g->peers[i].real_pk)) { + if (g->peers[i].gid < 0) { + return false; + } + + me = i; + my_gid = g->peers[i].gid; + break; + } + } - return 0; + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (me != i && my_gid == g->peers[i].gid) { + return true; + } + } + + return false; } -static void handle_direct_packet(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, - int close_index, void *userdata) +static void handle_direct_packet(Group_Chats *g_c, int32_t groupnumber, const uint8_t *data, uint16_t length, + int64_t peer_index) { if (length == 0) { return; @@ -1740,14 +2418,13 @@ static void handle_direct_packet(Group_Chats *g_c, uint32_t groupnumber, const u 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); - } + kill_friend_connection(g_c->fr_c, g->peers[peer_index].friendcon_id); + g->peers[peer_index].friendcon_id = -1; + g->peers[peer_index].connected = false; + g->peers[peer_index].group_number = INVALID_GROUP_NUMBER; + break; } - break; - case PEER_QUERY_ID: { Group_c *g = get_group_c(g_c, groupnumber); @@ -1755,54 +2432,123 @@ static void handle_direct_packet(Group_Chats *g_c, uint32_t groupnumber, const u return; } - send_peers(g_c, groupnumber, g->close[close_index].number, g->close[close_index].group_number); - } + Group_Peer *peer = &g->peers[peer_index]; - break; + if (really_connected(peer)) { + send_peers(g_c, groupnumber, peer->friendcon_id, peer->group_number); + send_peer_nums(g_c, groupnumber, peer->friendcon_id, peer->group_number); + peer->need_send_peers = false; + } - case PEER_RESPONSE_ID: { - handle_send_peers(g_c, groupnumber, data + 1, length - 1, userdata); + break; } - break; + case PEER_RESPONSE_ID: { + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + int self_peer_gid = get_self_peer_gid(g); + uint32_t old_peers_num = g->numpeers; + + g->keep_leave = false; + g->disable_auto_join = false; + g->join_mode = false; + + accept_peers_list(g, groupnumber, data + 1, length - 1); + + if (g->numpeers > old_peers_num) { + g->need_send_name = true; + need_send_peers(g); + } + + if (self_peer_gid != get_self_peer_gid(g)) { + /* disallow change self peer gid by remote */ + + change_self_peer_gid(g_c, groupnumber, self_peer_gid); + + if (self_peer_gid_collision(g)) { + change_self_peer_gid(g_c, groupnumber, find_new_peer_gid(g)); + } + + if (self_peer_gid >= 0) { + group_new_peer_send(g_c, groupnumber, self_peer_gid, g->real_pk, + dht_get_self_public_key(g_c->m->dht)); + } + } + + 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) { + return; + } + + settitle(g, peer_index, data + 1, length - 1); + + break; } - break; + case PEER_GROUP_NUM_ID: { + if (length < (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint16_t) + 1)) { + return; + } + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return; + } + + --length; + ++data; + + for (; length > (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint16_t)); + length -= (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint16_t)), + data += (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint16_t))) { + set_peer_groupnum(g, data, net_ntohs(*(const uint16_t *)(data + CRYPTO_PUBLIC_KEY_SIZE))); + } + + break; + } } } -#define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1) - -/* Send message to all close except receiver (if receiver isn't -1) +/** + * Send message to all close except \p except_peer (if \p except_peer isn't INVALID_PEER_INDEX) * NOTE: this function appends the group chat number to the data passed to it. * - * return number of messages sent. + * @return number of messages sent. */ -static unsigned int send_message_all_close(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, - uint16_t length, int receiver) +static uint32_t send_message_all_close(const Group_Chats *g_c, int32_t groupnumber, const uint8_t *data, + uint16_t length, uint16_t except_peer) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return 0; } - uint16_t i, sent = 0; + uint32_t sent = 0; - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type != GROUPCHAT_CLOSE_ONLINE) { + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (i == except_peer) { continue; } - if ((int)i == receiver) { + const Group_Peer *peer = &g->peers[i]; + + if (!really_connected(peer)) { continue; } - if (send_packet_group_peer(g_c->fr_c, g->close[i].number, PACKET_ID_MESSAGE_CONFERENCE, g->close[i].group_number, data, - length)) { + if (send_packet_group_peer(g_c->fr_c, peer->friendcon_id, PACKET_ID_MESSAGE_CONFERENCE, + peer->group_number, data, length)) { ++sent; } } @@ -1810,79 +2556,114 @@ static unsigned int send_message_all_close(const Group_Chats *g_c, uint32_t grou return sent; } -/* Send lossy message to all close except receiver (if receiver isn't -1) +/** + * Send message to all close except \p except_peer (if \p except_peer isn't INVALID_PEER_INDEX) * NOTE: this function appends the group chat number to the data passed to it. * - * return number of messages sent. + * @return number of messages sent. */ -static unsigned int send_lossy_all_close(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, - uint16_t length, - int receiver) +static uint32_t send_lossy_all_close(const Group_Chats *g_c, int32_t groupnumber, const uint8_t *data, uint16_t length, + uint16_t receiver) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return 0; } - unsigned int i, sent = 0, num_connected_closest = 0, connected_closest[DESIRED_CLOSE_CONNECTIONS]; + uint32_t sent = 0; + uint8_t num_closest = 0; - for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { - if (g->close[i].type != GROUPCHAT_CLOSE_ONLINE) { + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (!really_connected(&g->peers[i]) || i == receiver) { continue; } - if ((int)i == receiver) { - continue; + uint8_t j; + + for (j = 0; j < DESIRED_CLOSE_CONNECTIONS; ++j) { + if (closest(g, j) && g->closest_peers[j] == i) { + j = DESIRED_CLOSE_CONNECTIONS + 100; + ++num_closest; + break; + } } - if (g->close[i].closest) { - connected_closest[num_connected_closest] = i; - ++num_connected_closest; + if (j == DESIRED_CLOSE_CONNECTIONS + 100) { continue; } - if (send_lossy_group_peer(g_c->fr_c, g->close[i].number, PACKET_ID_LOSSY_CONFERENCE, g->close[i].group_number, data, + if (send_lossy_group_peer(g_c->fr_c, g->peers[i].friendcon_id, PACKET_ID_LOSSY_CONFERENCE, g->peers[i].group_number, + data, length)) { ++sent; } } - if (!num_connected_closest) { + if (!num_closest) { return sent; } - unsigned int to_send = 0; + int32_t to_send = -1; uint64_t comp_val_old = ~0; - for (i = 0; i < num_connected_closest; ++i) { - 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); + for (uint32_t i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + + if (!closest(g, i)) { + continue; + } + + uint16_t peer_index = g->closest_peers[i]; + + if (peer_index == receiver) { + continue; + } + + const Group_Peer *peer = &g->peers[peer_index]; + + + if (!really_connected(peer)) { + continue; + } + + uint64_t comp_val = calculate_comp_value(g->real_pk, peer->real_pk); if (comp_val < comp_val_old) { - to_send = connected_closest[i]; + to_send = peer_index; comp_val_old = comp_val; } } - if (send_lossy_group_peer(g_c->fr_c, g->close[to_send].number, PACKET_ID_LOSSY_CONFERENCE, - g->close[to_send].group_number, data, length)) { + if (to_send >= 0 && send_lossy_group_peer(g_c->fr_c, g->peers[to_send].friendcon_id, PACKET_ID_LOSSY_CONFERENCE, + g->peers[to_send].group_number, data, length)) { ++sent; } - unsigned int to_send_other = 0; + int32_t to_send_other = -1; comp_val_old = ~0; - for (i = 0; i < num_connected_closest; ++i) { - 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); + for (uint32_t i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { + + if (!closest(g, i)) { + continue; + } + + uint16_t peer_index = g->closest_peers[i]; + + if (peer_index == receiver) { + continue; + } + + const Group_Peer *peer = &g->peers[peer_index]; + + if (!really_connected(peer)) { + continue; + } + + uint64_t comp_val = calculate_comp_value(peer->real_pk, g->real_pk); if (comp_val < comp_val_old) { - to_send_other = connected_closest[i]; + to_send_other = peer_index; comp_val_old = comp_val; } } @@ -1891,44 +2672,87 @@ static unsigned int send_lossy_all_close(const Group_Chats *g_c, uint32_t groupn return sent; } - if (send_lossy_group_peer(g_c->fr_c, g->close[to_send_other].number, PACKET_ID_LOSSY_CONFERENCE, - g->close[to_send_other].group_number, data, length)) { + if (to_send_other >= 0 && send_lossy_group_peer(g_c->fr_c, g->peers[to_send_other].friendcon_id, + PACKET_ID_LOSSY_CONFERENCE, g->peers[to_send_other].group_number, + data, length)) { ++sent; } return sent; } -#define MAX_GROUP_MESSAGE_DATA_LEN (MAX_CRYPTO_DATA_SIZE - (1 + MIN_MESSAGE_PACKET_LEN)) +static int8_t group_packet_index(uint8_t msg_id) +{ + switch (msg_id) { + case GROUP_MESSAGE_PING_ID: + return 0; + + case GROUP_MESSAGE_UNSUBSCRIBE_ID: + return 1; + + case GROUP_MESSAGE_NICKNAME_ID: + return 2; + + case GROUP_MESSAGE_NEW_PEER_ID: + return 3; + + case GROUP_MESSAGE_KILL_PEER_ID: + return 4; + + case GROUP_MESSAGE_NAME_ID: + return 5; + + case GROUP_MESSAGE_TITLE_ID: + return 6; + + case PACKET_ID_MESSAGE: + return 7; + + case PACKET_ID_ACTION: + return 8; + } + + return -1; +} + /* Send data of len with message_id to groupnumber. * - * return number of peers it was sent to on success. + * return number of peers it was sent to on success (never 0). * return -1 if groupnumber is invalid. * return -2 if message is too long. * return -3 if we are not connected to the group. * reutrn -4 if message failed to send. + * reutrn -5 if unknown message_id. */ -static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint8_t message_id, const uint8_t *data, - uint16_t len) +static int64_t send_message_group(const Group_Chats *g_c, int32_t groupnumber, uint8_t message_id, + const uint8_t *data, uint16_t len) { + if (len > MAX_GROUP_MESSAGE_DATA_LEN) { + return -2; + } + Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - if (len > MAX_GROUP_MESSAGE_DATA_LEN) { - return -2; - } + Group_Peer *self_peer = get_self_peer(g); + + int source_peer_gid = self_peer ? self_peer->gid : -1; - if (g->status != GROUPCHAT_STATUS_CONNECTED) { + if (source_peer_gid < 0) { return -3; } - VLA(uint8_t, packet, sizeof(uint16_t) + sizeof(uint32_t) + 1 + len); - uint16_t peer_num = net_htons(g->peer_number); - memcpy(packet, &peer_num, sizeof(peer_num)); + int8_t pindex = group_packet_index(message_id); + + if (pindex < 0) { + return -5; + } + + self_peer->last_message_number[pindex] = g->message_number; ++g->message_number; @@ -1936,7 +2760,14 @@ 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); + uint32_t msgnum = g->message_number; + + uint8_t packet[MAX_GROUP_MESSAGE_DATA_LEN + sizeof(uint16_t) + sizeof(uint32_t) + 1]; + size_t packet_len = sizeof(uint16_t) + sizeof(uint32_t) + 1 + len; + uint16_t peer_gid = net_htons(source_peer_gid); + memcpy(packet, &peer_gid, sizeof(peer_gid)); + + uint32_t message_num = net_htonl(msgnum); memcpy(packet + sizeof(uint16_t), &message_num, sizeof(message_num)); packet[sizeof(uint16_t) + sizeof(uint32_t)] = message_id; @@ -1945,7 +2776,7 @@ static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint memcpy(packet + sizeof(uint16_t) + sizeof(uint32_t) + 1, data, len); } - unsigned int ret = send_message_all_close(g_c, groupnumber, packet, SIZEOF_VLA(packet), -1); + uint32_t ret = send_message_all_close(g_c, groupnumber, packet, (uint16_t)packet_len, -1); return (ret == 0) ? -4 : ret; } @@ -1954,30 +2785,30 @@ static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint * return 0 on success * see: send_message_group() for error codes. */ -int group_message_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *message, uint16_t length) +int group_message_send(const Group_Chats *g_c, int groupnumber, const uint8_t *message, uint16_t length) { - int ret = send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length); + int64_t ret = send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length); if (ret > 0) { return 0; } - return ret; + return (int)ret; } /* send a group action * return 0 on success * see: send_message_group() for error codes. */ -int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *action, uint16_t length) +int group_action_send(const Group_Chats *g_c, int groupnumber, const uint8_t *action, uint16_t length) { - int ret = send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length); + int64_t ret = send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length); if (ret > 0) { return 0; } - return ret; + return (int)ret; } /* High level function to send custom lossy packets. @@ -1985,23 +2816,30 @@ int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_ * return -1 on failure. * return 0 on success. */ -int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length) +int send_group_lossy_packet(const Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length) { - // TODO(irungentoo): length check here? Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - VLA(uint8_t, packet, sizeof(uint16_t) * 2 + length); - uint16_t peer_number = net_htons(g->peer_number); - memcpy(packet, &peer_number, sizeof(uint16_t)); + uint8_t packet[MAX_CRYPTO_DATA_SIZE]; + size_t plen = sizeof(uint16_t) * 2 + length; + + int self_peer_gid = get_self_peer_gid(g); + + if (self_peer_gid < 0) { + return -1; + } + + uint16_t peer_gid = net_htons((uint16_t)self_peer_gid); + memcpy(packet, &peer_gid, sizeof(uint16_t)); 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); - if (send_lossy_all_close(g_c, groupnumber, packet, SIZEOF_VLA(packet), -1) == 0) { + if (send_lossy_all_close(g_c, groupnumber, packet, (uint16_t)plen, -1) == 0) { return -1; } @@ -2009,8 +2847,8 @@ int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const return 0; } -static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, - int close_index, void *userdata) +static void handle_message_packet_group(Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, + uint16_t peer_index_from, void *userdata) { if (length < sizeof(uint16_t) + sizeof(uint32_t) + 1) { return; @@ -2022,61 +2860,137 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, return; } - uint16_t peer_number; - memcpy(&peer_number, data, sizeof(uint16_t)); - peer_number = net_ntohs(peer_number); + uint16_t from_peer_gid = net_ntohs(*(const uint16_t *)data); + int64_t index = get_peer_index(g, from_peer_gid); - int index = get_peer_index(g, peer_number); + uint8_t msg_id = data[sizeof(uint16_t) + sizeof(uint32_t)]; - 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.) */ - send_peer_query(g_c, g->close[close_index].number, g->close[close_index].group_number); + int8_t pindex = group_packet_index(msg_id); + + if (pindex < 0) { return; } - uint32_t message_number; - memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); - message_number = net_ntohl(message_number); + if (index == -1) { + if (really_connected(&g->peers[peer_index_from])) { + send_peer_query(g_c, g->peers[peer_index_from].friendcon_id, g->peers[peer_index_from].group_number); + } - if (g->group[index].last_message_number == 0) { - g->group[index].last_message_number = message_number; - } else if (message_number - g->group[index].last_message_number > 64 || - message_number == g->group[index].last_message_number) { - return; + if (msg_id != GROUP_MESSAGE_NEW_PEER_ID) { + return; /* this packet is very important to autojoin and compatibility, so we proceed id even no source peer known */ + } } - g->group[index].last_message_number = message_number; + bool allow_resend = false; + + if (index >= 0) { + if (!id_equal(g->real_pk, g->peers[index].real_pk)) { + allow_resend = true; + } + + uint32_t message_number; + memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); + message_number = ntohl(message_number); + + if (g->peers[index].last_message_number[pindex] > 0 && message_number == g->peers[index].last_message_number[pindex]) { + return; + } + + if (message_number < g->peers[index].last_message_number[pindex]) { + /* accept only increasing message numbers to avoid infinite loops */ + return; + } - 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); + g->peers[index].last_message_number[pindex] = message_number; + } + + const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(uint32_t) + 1; + uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(uint32_t) + 1); - switch (message_id) { + switch (msg_id) { case GROUP_MESSAGE_PING_ID: { - if (msg_data_len != 0) { + /* Unlike other packets, we don't check the size of the ping packet, + * because we don't care. Garbage in the packet is ignored. */ + Group_Peer *peer = &g->peers[index]; + peer->last_recv = unix_time(); + + if (peer->nick_len == 0 && peer->gid >= 0) { + /* empty nick */ + nick_request_send(g_c, groupnumber, peer->gid); + } + + if (peer->keep_connection) { + --peer->keep_connection; + } + } + break; + + case GROUP_MESSAGE_UNSUBSCRIBE_ID: { + if (msg_data_len < (GROUP_IDENTIFIER_LENGTH + CRYPTO_PUBLIC_KEY_SIZE)) { return; } - g->group[index].last_recv = unix_time(); + unsubscribe_peer(g_c, msg_data + 1, msg_data + GROUP_IDENTIFIER_LENGTH, (UnsubscribeType)msg_data[0]); } break; case GROUP_MESSAGE_NEW_PEER_ID: { - if (msg_data_len != GROUP_MESSAGE_NEW_PEER_LENGTH) { + if (msg_data_len < GROUP_MESSAGE_NEW_PEER_LENGTH) { return; } - uint16_t new_peer_number; - 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); + uint16_t new_peer_gid; + memcpy(&new_peer_gid, msg_data, sizeof(uint16_t)); + new_peer_gid = net_ntohs(new_peer_gid); + + if (from_peer_gid == new_peer_gid) { + allow_resend = false; + } + + const uint8_t *real_pk = msg_data + sizeof(uint16_t); + const uint8_t *temp_pk = real_pk + CRYPTO_PUBLIC_KEY_SIZE; + + if (id_equal(g->real_pk, real_pk)) { + allow_resend = false; + g->need_send_name = true; + g->keep_leave = false; + g->disable_auto_join = false; + g->join_mode = false; + } + + /*int64_t peer_index =*/ addpeer(g, groupnumber, real_pk, temp_pk, new_peer_gid); + + int self_peer_gid = get_self_peer_gid(g); + + if (self_peer_gid >= 0 && self_peer_gid_collision(g)) { + self_peer_gid = find_new_peer_gid(g); + change_self_peer_gid(g_c, groupnumber, self_peer_gid); + group_new_peer_send(g_c, groupnumber, (uint16_t)self_peer_gid, g->real_pk, + dht_get_self_public_key(g_c->m->dht)); + } + } + break; + + case GROUP_MESSAGE_NICKNAME_ID: { + if (msg_data_len < sizeof(uint16_t)) { + return; + } + + uint16_t gid; + memcpy(&gid, msg_data, sizeof(uint16_t)); + gid = net_ntohs(gid); + + Group_Peer *self = get_self_peer(g); + + if (self && gid == self->gid) { + self->nick_changed = true; + g->nick_changed = true; + } } break; case GROUP_MESSAGE_KILL_PEER_ID: { - if (msg_data_len != GROUP_MESSAGE_KILL_PEER_LENGTH) { + if (msg_data_len < GROUP_MESSAGE_KILL_PEER_LENGTH) { return; } @@ -2084,24 +2998,30 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, memcpy(&kill_peer_number, msg_data, sizeof(uint16_t)); kill_peer_number = net_ntohs(kill_peer_number); - if (peer_number == kill_peer_number) { - delpeer(g_c, groupnumber, index, userdata); - } else { - return; - // TODO(irungentoo): + if (kill_peer_number == from_peer_gid) { + const Group_c *g2 = get_group_c(g_c, groupnumber); + // TODO(iphydf): Verify that this is always true, then s/g2/g/ + // and remove the above declaration and function call. + assert(g2 == g); + + if (g2) { + unsubscribe_peer(g_c, g2->identifier + 1, g2->peers[index].real_pk, UNS_TEMP); + } + + delpeer(g_c, groupnumber, index); } } break; case GROUP_MESSAGE_NAME_ID: { - if (setnick(g_c, groupnumber, index, msg_data, msg_data_len, userdata, true) == -1) { + if (!setnick(g, index, msg_data, msg_data_len)) { return; } } break; case GROUP_MESSAGE_TITLE_ID: { - if (settitle(g_c, groupnumber, index, msg_data, msg_data_len, userdata) == -1) { + if (!settitle(g, index, msg_data, msg_data_len)) { return; } } @@ -2112,13 +3032,17 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, return; } - VLA(uint8_t, newmsg, msg_data_len + 1); + uint8_t newmsg[MAX_CRYPTO_DATA_SIZE]; memcpy(newmsg, msg_data, msg_data_len); newmsg[msg_data_len] = 0; - // TODO(irungentoo): if (g_c->message_callback) { - g_c->message_callback(g_c->m, groupnumber, index, 0, newmsg, msg_data_len, userdata); + int index_in_list = find_peer_index_in_list(g, index); + + if (index_in_list >= 0) { + g_c->message_callback(g_c->m, (uint32_t)groupnumber, (uint32_t)index_in_list, + 0, newmsg, msg_data_len, userdata); + } } break; @@ -2129,23 +3053,31 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, return; } - VLA(uint8_t, newmsg, msg_data_len + 1); + uint8_t newmsg[MAX_CRYPTO_DATA_SIZE]; memcpy(newmsg, msg_data, msg_data_len); newmsg[msg_data_len] = 0; - // TODO(irungentoo): if (g_c->message_callback) { - g_c->message_callback(g_c->m, groupnumber, index, 1, newmsg, msg_data_len, userdata); + int index_in_list = find_peer_index_in_list(g, index); + + if (index_in_list >= 0) { + g_c->message_callback(g_c->m, (uint32_t)groupnumber, (uint32_t)index_in_list, + 1, newmsg, msg_data_len, userdata); + } } break; } - default: + default: { return; + } } - send_message_all_close(g_c, groupnumber, data, length, -1/* TODO(irungentoo) close_index */); + if (allow_resend) { + send_message_all_close(g_c, groupnumber, data, length, msg_id == PACKET_ID_MESSAGE + || msg_id == PACKET_ID_ACTION ? -1 : peer_index_from); + } } static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) @@ -2167,27 +3099,35 @@ static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t groupnumber; memcpy(&groupnumber, data + 1, sizeof(uint16_t)); groupnumber = net_ntohs(groupnumber); - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } - int index = friend_in_close(g, friendcon_id); + int64_t peer_index = -1; - if (index == -1) { + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->peers[i].friendcon_id == friendcon_id) { + peer_index = i; + break; + } + } + + if (peer_index == -1) { return -1; } switch (data[0]) { case PACKET_ID_DIRECT_CONFERENCE: { - handle_direct_packet(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index, userdata); + handle_direct_packet(g_c, groupnumber, data + 1 + sizeof(uint16_t), + length - (1 + sizeof(uint16_t)), peer_index); break; } case PACKET_ID_MESSAGE_CONFERENCE: { - handle_message_packet_group(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index, - userdata); + handle_message_packet_group(g_c, groupnumber, data + 1 + sizeof(uint16_t), + length - (1 + sizeof(uint16_t)), peer_index, userdata); break; } @@ -2207,57 +3147,63 @@ static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, * * TODO(irungentoo): test this */ -static unsigned int lossy_packet_not_received(Group_c *g, int peer_index, uint16_t message_number) +static int8_t lossy_packet_not_received(Group_c *g, int64_t peer_index, uint16_t message_number) { if (peer_index == -1) { - // TODO(sudden6): invalid return value return -1; } - if (g->group[peer_index].bottom_lossy_number == g->group[peer_index].top_lossy_number) { - g->group[peer_index].top_lossy_number = message_number; - g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; - g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + Group_Peer *peer = &g->peers[peer_index]; + Group_Peer_Lossy *lossy = peer->lossy; + + if (!lossy) { + lossy = (Group_Peer_Lossy *)calloc(1, sizeof(Group_Peer_Lossy)); + + if (lossy == nullptr) { + return -1; + } + + peer->lossy = lossy; + } + + if (lossy->bottom_lossy_number == lossy->top_lossy_number) { + lossy->top_lossy_number = message_number; + lossy->bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; + lossy->recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } - if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) < MAX_LOSSY_COUNT) { - if (g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT]) { + if ((uint16_t)(message_number - lossy->bottom_lossy_number) < MAX_LOSSY_COUNT) { + if (lossy->recv_lossy[message_number % MAX_LOSSY_COUNT]) { return 1; } - g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + lossy->recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } - if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) > (1 << 15)) { - // TODO(sudden6): invalid return value + if ((uint16_t)(message_number - lossy->bottom_lossy_number) > (1 << 15)) { return -1; } - uint16_t top_distance = message_number - g->group[peer_index].top_lossy_number; + uint16_t top_distance = message_number - lossy->top_lossy_number; if (top_distance >= MAX_LOSSY_COUNT) { - crypto_memzero(g->group[peer_index].recv_lossy, sizeof(g->group[peer_index].recv_lossy)); - g->group[peer_index].top_lossy_number = message_number; - g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; - g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; - - return 0; - } else { // top_distance < MAX_LOSSY_COUNT - unsigned int i; - - for (i = g->group[peer_index].bottom_lossy_number; i != (g->group[peer_index].bottom_lossy_number + top_distance); - ++i) { - g->group[peer_index].recv_lossy[i % MAX_LOSSY_COUNT] = 0; + crypto_memzero(lossy->recv_lossy, sizeof(lossy->recv_lossy)); + lossy->top_lossy_number = message_number; + lossy->bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; + lossy->recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; + } else { + for (size_t i = lossy->bottom_lossy_number; i != (lossy->bottom_lossy_number + top_distance); ++i) { + lossy->recv_lossy[i % MAX_LOSSY_COUNT] = 0; } - g->group[peer_index].top_lossy_number = message_number; - g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; - g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; - - return 0; + lossy->top_lossy_number = message_number; + lossy->bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; + lossy->recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; } + + return 0; } static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) @@ -2272,12 +3218,12 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin return -1; } - uint16_t groupnumber, peer_number, message_number; + uint16_t groupnumber, peer_gid, message_number; memcpy(&groupnumber, data + 1, sizeof(uint16_t)); - memcpy(&peer_number, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); + memcpy(&peer_gid, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); memcpy(&message_number, data + 1 + sizeof(uint16_t) * 2, sizeof(uint16_t)); groupnumber = net_ntohs(groupnumber); - peer_number = net_ntohs(peer_number); + peer_gid = net_ntohs(peer_gid); message_number = net_ntohs(message_number); Group_c *g = get_group_c(g_c, groupnumber); @@ -2286,17 +3232,24 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin return -1; } - int index = friend_in_close(g, friendcon_id); + int64_t peer_index_from = -1; - if (index == -1) { + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->peers[i].friendcon_id == friendcon_id) { + peer_index_from = i; + break; + } + } + + if (peer_index_from == -1) { return -1; } - if (peer_number == g->peer_number) { + if ((int)peer_gid == get_self_peer_gid(g)) { return -1; } - int peer_index = get_peer_index(g, peer_number); + int64_t peer_index = get_peer_index(g, peer_gid); if (peer_index == -1) { return -1; @@ -2308,12 +3261,21 @@ 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; + int index_in_list = find_peer_index_in_list(g, peer_index); + + if (index_in_list < 0) { + return -1; + } + +#if 0 + if (g_c->lossy_packethandlers[message_id].function) { - if (g_c->lossy_packethandlers[message_id].function(g->object, groupnumber, peer_index, g->group[peer_index].object, + if (g_c->lossy_packethandlers[message_id].function(g->object, (int)groupnumber, index_in_list, + g->peers[peer_index].object, lossy_data, lossy_length) == -1) { return -1; } @@ -2321,16 +3283,27 @@ static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uin return -1; } - send_lossy_all_close(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); - return 0; -} +#endif + + if (192 /*GROUP_AUDIO_PACKET_ID*/ == message_id && g_c->lossy_packethandler) { + if (g_c->lossy_packethandler(g->object, (int)groupnumber, index_in_list, g->peers[peer_index].object, + lossy_data, lossy_length) == -1) { + return -1; + } + } else { + return -1; + } + + send_lossy_all_close(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), peer_index_from); + return 0; +} /* Set the object that is tied to the group chat. * * return 0 on success. * return -1 on failure */ -int group_set_object(const Group_Chats *g_c, uint32_t groupnumber, void *object) +int group_set_object(const Group_Chats *g_c, int groupnumber, void *object) { Group_c *g = get_group_c(g_c, groupnumber); @@ -2347,7 +3320,7 @@ int group_set_object(const Group_Chats *g_c, uint32_t groupnumber, void *object) * return 0 on success. * return -1 on failure */ -int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, void *object) +int group_peer_set_object(const Group_Chats *g_c, int groupnumber, int peernumber, void *object) { Group_c *g = get_group_c(g_c, groupnumber); @@ -2359,7 +3332,11 @@ int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, int peer return -1; } - g->group[peernumber].object = object; + if (g->peers[peernumber].object && g->peer_on_leave) { + g->peer_on_leave(g->object, groupnumber, g->peers[peernumber].object); + } + + g->peers[peernumber].object = object; return 0; } @@ -2368,9 +3345,9 @@ int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, int peer * return NULL on failure. * return object on success. */ -void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber) +void *group_get_object(const Group_Chats *g_c, int groupnumber) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return nullptr; @@ -2384,9 +3361,9 @@ void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber) * return NULL on failure. * return object on success. */ -void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, int peernumber) +void *group_peer_get_object(const Group_Chats *g_c, int groupnumber, int peernumber) { - Group_c *g = get_group_c(g_c, groupnumber); + const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return nullptr; @@ -2396,13 +3373,300 @@ void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, int pe return nullptr; } - return g->group[peernumber].object; + return g->peers[peernumber].object; } -/* Interval in seconds to send ping messages */ -#define GROUP_PING_INTERVAL 20 +static int64_t deltatime(uint64_t t1, uint64_t t2) +{ + return (int64_t)(t1 - t2); +} + +static bool possible_groupnum(Group_Chats *g_c, Group_c *ig, uint16_t gn, const uint8_t *for_pk) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + const Group_c *g = get_group_c(g_c, i); + + if (g == ig) { + continue; + } + + if (!g) { + continue; + } + + int64_t peer_index = peer_in_chat(g, for_pk); + + if (peer_index >= 0 && g->peers[peer_index].group_number == gn) { + /* same peer already in gn group, so, current group has other gn value */ + return false; + } + } + + return true; +} + +static Group_Join_Peer *keep_join_mode(Group_Chats *g_c, Group_c *g, uint64_t ct) +{ + if (g->keep_join_index < 0) { + return nullptr; + } + + if (g->keep_join_index >= (int)g->numjoinpeers) { + g->keep_join_index = 0; + } + + int next_time = JOIN_CHECK_DELAY_MS; + Group_Join_Peer *jd = &g->joinpeers[g->keep_join_index]; + + int friend_index = getfriend_id(g_c->m, jd->real_pk); + int friendcon_id = friend_index >= 0 ? getfriendcon_id(g_c->m, friend_index) : -1; + + if (friendcon_id >= 0 && friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED + && !jd->unsubscribed) { + next_time = KEEP_JOIN_ATTEMPT_DELAY_MS; + + const int mcount = g->numpeers; /* 1 - me */ + + bool present = false; + + for (int m = 0; m < mcount; ++m) { + if (id_equal(g->peers[m].real_pk, jd->real_pk)) { + next_time = 1000; + present = true; + break; + } + } + + if (!present) { + /* wow, member is online, but not in conference? restore! */ + if (jd->fails >= MAX_FAILED_JOIN_ATTEMPTS) { + jd->fails = 0; + jd->online = true; + } + + jd->next_try_time = ct; + return jd; + } + } + + g->next_join_check_time = ct + next_time; + ++g->keep_join_index; + + return nullptr; +} + +typedef struct { + uint64_t ct; + + Group_Chats *g_c; + Group_c *g; + Group_Join_Peer *peer; + + uint16_t group_number; + uint32_t join_peer_index; +} jp_iterator; + +static Group_Join_Peer *jp_iterator_next(jp_iterator *itr) +{ + for (;;) { + if (itr->group_number >= itr->g_c->num_chats) { + itr->g = nullptr; + itr->peer = nullptr; + return nullptr; + } + + if (itr->g == nullptr) { + itr->join_peer_index = 0; + itr->g = get_group_c(itr->g_c, itr->group_number); + + if (itr->g == nullptr) { + ++itr->group_number; + continue; + } + } + + if (itr->join_peer_index >= itr->g->numjoinpeers || !itr->g->join_mode || itr->g->disable_auto_join + || itr->g->keep_leave + || (itr->ct != 0 && deltatime(itr->ct, itr->g->next_join_check_time) < 0)) { + itr->g = nullptr; + itr->join_peer_index = 0; + ++itr->group_number; + continue; + } + + itr->peer = &itr->g->joinpeers[itr->join_peer_index]; + ++itr->join_peer_index; + return itr->peer; + } +} + +static Group_Join_Peer *jp_iterator_setup(jp_iterator *itr, Group_Chats *g_c, uint64_t t) +{ + itr->ct = t; + itr->g_c = g_c; + itr->g = nullptr; + itr->group_number = 0; + itr->join_peer_index = 0; + itr->peer = nullptr; + return jp_iterator_next(itr); +} + +static void set_next_join_try(Group_Chats *g_c, const uint8_t *real_pk, uint64_t t) +{ + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + Group_c *g = get_group_c(g_c, i); + + if (!(g && g->join_mode)) { + continue; + } + + for (uint16_t j = 0; j < g->numjoinpeers; ++j) { + if (id_equal(g->joinpeers[j].real_pk, real_pk)) { + g->joinpeers[j].next_try_time = t; + break; + } + } + } +} -static int ping_groupchat(Group_Chats *g_c, uint32_t groupnumber) +static void restore_conference(Group_Chats *g_c) +{ + uint64_t now = current_time_monotonic(); + + Group_Join_Peer *jd = nullptr; + Group_c *g = nullptr; + + int8_t min_num = MAX_FAILED_JOIN_ATTEMPTS; + + bool at_max = false; + bool on_try = false; + + jp_iterator jpi; + + for (Group_Join_Peer *j = jp_iterator_setup(&jpi, g_c, now); j; j = jp_iterator_next(&jpi)) { + if (j->unsubscribed) { + j->fails = MAX_FAILED_JOIN_ATTEMPTS; + } + + if (j->fails >= MAX_FAILED_JOIN_ATTEMPTS) { + at_max = true; + continue; + } + + int32_t friend_index = getfriend_id(g_c->m, j->real_pk); + + if (friend_index < 0) { + j->fails = MAX_FAILED_JOIN_ATTEMPTS; /* not a friend - skip */ + at_max = true; + continue; + } + + int friendcon_id = getfriendcon_id(g_c->m, friend_index); + + if (friendcon_id >= 0 && friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { + if (!j->online) { + j->online = true; + j->fails = 0; + j->next_try_time = now + 3000; /* wait 3 sec */ + jpi.g->next_join_check_time = now + JOIN_CHECK_DELAY_MS; + } + } + + on_try = true; + + if (deltatime(now, j->next_try_time) < 0) { + continue; + } + + if (j->fails < min_num) { + jd = j; + g = jpi.g; + min_num = j->fails; + } + } + + if (jd == nullptr) { + if (at_max && !on_try) { + /* now restart join process */ + for (Group_Join_Peer *j = jp_iterator_setup(&jpi, g_c, 0); j; j = jp_iterator_next(&jpi)) { + j->online = false; + j->unsubscribed = false; + j->fails = 0; + j->next_try_time = now; + + jpi.g->next_join_check_time = now + JOIN_ATTEMPT_DELAY_MS; + } + + return; + } + + if (!at_max && !on_try) { + /* keep_join_index */ + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + g = get_group_c(g_c, i); + + if (!g) { + continue; + } + + if (deltatime(now, g->next_join_check_time) < 0) { + continue; + } + + g->next_join_check_time = now + KEEP_JOIN_ATTEMPT_DELAY_MS; + + if (g->join_mode || g->keep_leave || g->disable_auto_join || g->numjoinpeers == 0) { + continue; + } + + if (g->keep_join_index < 0) { + g->keep_join_index = 0; + } + + jd = keep_join_mode(g_c, g, now); + + if (jd) { + break; + } + } + } + } + + if (!jd) { + return; + } + + int friend_index = jd->online ? getfriend_id(g_c->m, jd->real_pk) : -1; + + if (friend_index >= 0) { + while (!possible_groupnum(g_c, g, jd->fails, jd->real_pk)) { + ++jd->fails; + }; + + uint8_t invite_data[GROUP_IDENTIFIER_LENGTH + sizeof(uint16_t)]; + + net_pack_u16(invite_data, jd->fails); + + memcpy(invite_data + 2, g->identifier, GROUP_IDENTIFIER_LENGTH); + + g->auto_join = true; + + join_groupchat(g_c, friend_index, g->identifier[0], invite_data, sizeof(invite_data)); + + g->auto_join = false; + + ++jd->fails; + + set_next_join_try(g_c, jd->real_pk, now + JOIN_ATTEMPT_DELAY_MS); + } else { + ++jd->fails; + } + + g->next_join_check_time = now + JOIN_CHECK_DELAY_MS; +} + +static int ping_groupchat(Group_Chats *g_c, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); @@ -2413,57 +3677,68 @@ static int ping_groupchat(Group_Chats *g_c, uint32_t groupnumber) if (is_timeout(g->last_sent_ping, GROUP_PING_INTERVAL)) { if (group_ping_send(g_c, groupnumber) != -1) { /* Ping */ g->last_sent_ping = unix_time(); + } else { + /* no peers to ping */ + g->last_sent_ping = unix_time() - GROUP_PING_INTERVAL + 2; } + } return 0; } -static int groupchat_clear_timedout(Group_Chats *g_c, uint32_t groupnumber, void *userdata) +static void groupchat_clear_timedout(Group_Chats *g_c, int groupnumber, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { - return -1; + return; } - uint32_t i; + uint32_t opc = 0, dp = 0; - for (i = 0; i < g->numpeers; ++i) { - if (g->peer_number != g->group[i].peer_number && is_timeout(g->group[i].last_recv, GROUP_PING_INTERVAL * 3)) { - delpeer(g_c, groupnumber, i, userdata); + for (uint32_t i = 0; i < g->numpeers; ++i) { + if (g->peers[i].friendcon_id == ALMOST_DELETED_PEER || id_equal(g->peers[i].real_pk, g->real_pk)) { + continue; } - if (g->group == nullptr || i >= g->numpeers) { - break; + ++opc; + + if (is_timeout(g->peers[i].last_recv, GROUP_PING_INTERVAL * 3)) { + delpeer(g_c, groupnumber, i); + ++dp; } } - return 0; + if (opc == dp && opc) { + /* looks like lost connection to other peers */ + on_offline(g); + } } /* Send current name (set in messenger) to all online groups. */ void send_name_all_groups(Group_Chats *g_c) { - unsigned int i; - - for (i = 0; i < g_c->num_chats; ++i) { - Group_c *g = get_group_c(g_c, i); - - if (!g) { - continue; - } - - if (g->status == GROUPCHAT_STATUS_CONNECTED) { + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + if (is_groupnumber_valid(g_c, i)) { group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); } } } +static uint32_t saved_conferences_size(const Messenger *m); +static void conferences_save(const Messenger *m, uint8_t *data); +static int conferences_load(Messenger *m, const uint8_t *data, uint32_t length); + /* Create new groupchat instance. */ Group_Chats *new_groupchats(Messenger *m) { + // HACK HACK HACK for Messenger.c. + saved_conferences_size_ptr = saved_conferences_size; + conferences_save_ptr = conferences_save; + conferences_load_ptr = conferences_load; + if (!m) { return nullptr; } @@ -2485,32 +3760,56 @@ Group_Chats *new_groupchats(Messenger *m) /* main groupchats loop. */ void do_groupchats(Group_Chats *g_c, void *userdata) { - unsigned int i; + bool is_online = onion_connection_status(g_c->m->onion_c) != 0; - for (i = 0; i < g_c->num_chats; ++i) { - Group_c *g = get_group_c(g_c, i); + if (!is_online && g_c->is_online) { + /* to offline */ - if (!g) { + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + Group_c *g = &g_c->chats[i]; + + if (!g->live || g->disable_auto_join) { + continue; + } + + disconnect_conference(g_c, i, UNS_NONE); + on_offline(g); + } + } + + g_c->is_online = is_online; + + if (!is_online) { + return; + } + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + const Group_c *g = &g_c->chats[i]; + + if (!g->live) { continue; } - if (g->status == GROUPCHAT_STATUS_CONNECTED) { - connect_to_closest(g_c, i, userdata); - ping_groupchat(g_c, i); + if (g->disable_auto_join) { groupchat_clear_timedout(g_c, i, userdata); + apply_changes_in_peers(g_c, i, userdata); + continue; } + + apply_changes_in_peers(g_c, i, userdata); + connect_to_closest(g_c, i, userdata); + ping_groupchat(g_c, i); + groupchat_clear_timedout(g_c, i, userdata); } - // TODO(irungentoo): + restore_conference(g_c); /* always do something to restore contacts */ } /* Free everything related with group chats. */ void kill_groupchats(Group_Chats *g_c) { - unsigned int i; - - for (i = 0; i < g_c->num_chats; ++i) { - del_groupchat(g_c, i); + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + del_groupchat_internal(g_c, i, UNS_NONE); } m_callback_conference_invite(g_c->m, nullptr); @@ -2522,13 +3821,12 @@ void kill_groupchats(Group_Chats *g_c) * You should use this to determine how much memory to allocate * for copy_chatlist. */ -uint32_t count_chatlist(Group_Chats *g_c) +uint32_t count_chatlist(const Group_Chats *g_c) { uint32_t ret = 0; - uint32_t i; - for (i = 0; i < g_c->num_chats; i++) { - if (g_c->chats[i].status != GROUPCHAT_STATUS_NONE) { + for (uint16_t i = 0; i < g_c->num_chats; i++) { + if (g_c->chats[i].live) { ret++; } } @@ -2541,7 +3839,7 @@ uint32_t count_chatlist(Group_Chats *g_c) * Otherwise, returns the number of elements copied. * If the array was too small, the contents * of out_list will be truncated to list_size. */ -uint32_t copy_chatlist(Group_Chats *g_c, uint32_t *out_list, uint32_t list_size) +uint32_t copy_chatlist(const Group_Chats *g_c, uint32_t *out_list, uint32_t list_size) { if (!out_list) { return 0; @@ -2551,14 +3849,14 @@ uint32_t copy_chatlist(Group_Chats *g_c, uint32_t *out_list, uint32_t list_size) return 0; } - uint32_t i, ret = 0; + uint32_t ret = 0; - for (i = 0; i < g_c->num_chats; ++i) { + for (uint16_t i = 0; i < g_c->num_chats; ++i) { if (ret >= list_size) { break; /* Abandon ship */ } - if (g_c->chats[i].status > GROUPCHAT_STATUS_NONE) { + if (g_c->chats[i].live) { out_list[ret] = i; ret++; } @@ -2566,3 +3864,161 @@ uint32_t copy_chatlist(Group_Chats *g_c, uint32_t *out_list, uint32_t list_size) return ret; } + +static uint32_t saved_conferences_size(const Messenger *m) +{ + const Group_Chats *g_c = (Group_Chats *)m->conferences_object; + + size_t sz = sizeof(uint16_t); // size of number of groupchats + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + const Group_c *g = &g_c->chats[i]; + + if (!g->live) { + continue; + } + + /* +1 byte for options, +1 byte for title len, +2 bytes for count of joinpeers */ + sz += GROUP_IDENTIFIER_LENGTH + 1 + 1 + sizeof(uint16_t); + sz += g->title_len; /* +1 byte for title len */ + sz += g->numjoinpeers * CRYPTO_PUBLIC_KEY_SIZE; + } + + return (uint32_t)sz; +} + +static void conferences_save(const Messenger *m, uint8_t *data) +{ + Group_Chats *g_c = (Group_Chats *)m->conferences_object; + + uint16_t *num = (uint16_t *)data; + *num = 0; + data += sizeof(uint16_t); + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + const Group_c *g = &g_c->chats[i]; + + if (!g->live) { + continue; + } + + memcpy(data, g->identifier, GROUP_IDENTIFIER_LENGTH); + data += GROUP_IDENTIFIER_LENGTH; + + uint8_t options = 0; + + if (g->keep_leave) { + options = 1; + } + + *data = options; + ++data; + + *data = g->title_len; + ++data; + + memcpy(data, g->title, g->title_len); + data += g->title_len; + + data += net_pack_u16(data, g->numjoinpeers); + + for (uint16_t j = 0; j < g->numjoinpeers; ++j) { + memcpy(data, g->joinpeers[j].real_pk, CRYPTO_PUBLIC_KEY_SIZE); + data += CRYPTO_PUBLIC_KEY_SIZE; + } + + ++*num; + } +} + +static int conferences_load(Messenger *m, const uint8_t *data, uint32_t length) +{ + if (length < sizeof(uint16_t)) { + return -1; + } + + Group_Chats *g_c = (Group_Chats *)m->conferences_object; + + for (uint16_t i = 0; i < g_c->num_chats; ++i) { + del_groupchat_internal(g_c, i, UNS_NONE); + } + + g_c->num_chats = 0; + + const size_t numgchats = lendian_to_host16(*(const uint16_t *)data); + data += sizeof(uint16_t); + length -= sizeof(uint16_t); + + for (uint32_t i = 0; i < numgchats; ++i) { + if (length < GROUP_IDENTIFIER_LENGTH + 4) { + return -1; + } + + const int groupnumber = add_groupchat(g_c, *data, data + 1); + data += GROUP_IDENTIFIER_LENGTH; + length -= GROUP_IDENTIFIER_LENGTH; + + Group_c *g = get_group_c(g_c, groupnumber); + + if (!g) { + return -1; + } + + g->invite_called = false; + + if (*data & 1) { + g->keep_leave = true; + g->disable_auto_join = true; + } else { + g->join_mode = true; + } + + ++data; + --length; + + if (*data > sizeof(g->title)) { + del_groupchat_internal(g_c, groupnumber, UNS_NONE); + return -1; + } + + g->title_len = *data; + ++data; + --length; + + if (length < g->title_len) { + del_groupchat_internal(g_c, groupnumber, UNS_NONE); + return -1; + } + + memcpy(g->title, data, g->title_len); + data += g->title_len; + length -= g->title_len; + + net_unpack_u16(data, &g->numjoinpeers); + data += sizeof(uint16_t); + length -= sizeof(uint16_t); + + g->joinpeers = (Group_Join_Peer *)calloc(g->numjoinpeers, sizeof(Group_Join_Peer)); + + if (!g->joinpeers) { + return -1; + } + + if (length < g->numjoinpeers * CRYPTO_PUBLIC_KEY_SIZE) { + del_groupchat_internal(g_c, groupnumber, UNS_NONE); + return -1; + } + + length -= g->numjoinpeers * CRYPTO_PUBLIC_KEY_SIZE; + + const uint64_t t = current_time_monotonic() + KEEP_JOIN_ATTEMPT_DELAY_MS; + + for (uint16_t j = 0; j < g->numjoinpeers; ++j) { + memcpy(g->joinpeers[j].real_pk, data, CRYPTO_PUBLIC_KEY_SIZE); + data += CRYPTO_PUBLIC_KEY_SIZE; + g->joinpeers[j].next_try_time = t; + } + } + + return 0; +} diff --git a/toxcore/group.h b/toxcore/group.h index a81dac39db..f60efd6739 100644 --- a/toxcore/group.h +++ b/toxcore/group.h @@ -26,12 +26,6 @@ #include "Messenger.h" -enum { - GROUPCHAT_STATUS_NONE, - GROUPCHAT_STATUS_VALID, - GROUPCHAT_STATUS_CONNECTED -}; - enum { GROUPCHAT_TYPE_TEXT, GROUPCHAT_TYPE_AV @@ -39,81 +33,106 @@ enum { #define MAX_LOSSY_COUNT 256 +/* number of group messages */ +#define NUM_GROUP_PACKET_IDS 9 + +typedef struct { + uint8_t recv_lossy[MAX_LOSSY_COUNT]; + uint16_t bottom_lossy_number; + uint16_t top_lossy_number; +} Group_Peer_Lossy; + typedef struct { uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; - uint64_t last_recv; - uint32_t last_message_number; + Group_Peer_Lossy *lossy; /* rare use */ + void *object; - uint8_t nick[MAX_NAME_LENGTH]; - uint8_t nick_len; + uint64_t last_recv; - uint16_t peer_number; + uint8_t *nick; - uint8_t recv_lossy[MAX_LOSSY_COUNT]; - uint16_t bottom_lossy_number, top_lossy_number; + uint32_t last_message_number[NUM_GROUP_PACKET_IDS]; + int friendcon_id; - void *object; + signed gid : 24; /* unique per-conference peer id */ + uint8_t nick_len; + uint16_t group_number; + uint8_t keep_connection; /* keep connection even not in ring (count down every incoming ping packet) */ + unsigned nick_changed : 1; + unsigned title_changed : 1; + unsigned auto_join : 1; + unsigned need_send_peers : 1; + unsigned connected : 1; } Group_Peer; +typedef struct { + uint64_t next_try_time; + uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + int8_t fails; + unsigned online : 1; + unsigned unsubscribed : 1; +} Group_Join_Peer; + #define DESIRED_CLOSE_CONNECTIONS 4 -#define MAX_GROUP_CONNECTIONS 16 #define GROUP_IDENTIFIER_LENGTH (1 + CRYPTO_SYMMETRIC_KEY_SIZE) /* type + CRYPTO_SYMMETRIC_KEY_SIZE so we can use new_symmetric_key(...) to fill it */ -enum { - GROUPCHAT_CLOSE_NONE, - GROUPCHAT_CLOSE_CONNECTION, - GROUPCHAT_CLOSE_ONLINE -}; - typedef struct { - uint8_t status; + Group_Peer *peers; + Group_Join_Peer *joinpeers; + uint16_t *peers_list; + void *object; - Group_Peer *group; uint32_t numpeers; + uint32_t numpeers_in_list; + uint32_t message_number; + uint16_t numjoinpeers; - struct { - uint8_t type; /* GROUPCHAT_CLOSE_* */ - uint8_t closest; - uint32_t number; - uint16_t group_number; - } close[MAX_GROUP_CONNECTIONS]; + uint64_t last_sent_ping; + uint64_t next_join_check_time; + uint64_t last_close_check_time; uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; - struct { - uint8_t entry; - uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; - uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; - } closest_peers[DESIRED_CLOSE_CONNECTIONS]; - uint8_t changed; - - uint8_t identifier[GROUP_IDENTIFIER_LENGTH]; - uint8_t title[MAX_NAME_LENGTH]; - uint8_t title_len; - - uint32_t message_number; - uint16_t lossy_message_number; - uint16_t peer_number; - - uint64_t last_sent_ping; + uint16_t closest_peers[DESIRED_CLOSE_CONNECTIONS]; - int number_joined; /* friendcon_id of person that invited us to the chat. (-1 means none) */ + void (*peer_on_join)(void *, int32_t, int); + void (*peer_on_leave)(void *, int32_t, void *); + void (*group_on_delete)(void *, int32_t); - void *object; + uint8_t identifier[GROUP_IDENTIFIER_LENGTH]; - void (*peer_on_join)(void *, uint32_t, uint32_t); - void (*peer_on_leave)(void *, uint32_t, uint32_t, void *); - void (*group_on_delete)(void *, uint32_t); +unsigned closest_peers_entry : + DESIRED_CLOSE_CONNECTIONS; + unsigned live : 1; + unsigned join_mode : 1; + unsigned fake_join : 1; + unsigned auto_join : 1; + + unsigned title_len : 8; + unsigned lossy_message_number : 16; + + signed keep_join_index : 24; + + unsigned need_send_name : 1; + unsigned dirty_list : 1; + unsigned title_changed : 1; + unsigned invite_called : 1; + unsigned keep_leave : 1; + unsigned disable_auto_join : 1; + unsigned nick_changed : 1; } Group_c; typedef struct { Messenger *m; Friend_Connections *fr_c; + Group_c *chats; - uint32_t num_chats; + uint16_t num_chats; + unsigned is_online : 1; + void (*invite_callback)(Messenger *m, uint32_t, int, const uint8_t *, size_t, void *); void (*message_callback)(Messenger *m, uint32_t, uint32_t, int, const uint8_t *, size_t, void *); @@ -121,14 +140,26 @@ typedef struct { void (*peer_list_changed_callback)(Messenger *m, uint32_t, void *); void (*title_callback)(Messenger *m, uint32_t, uint32_t, const uint8_t *, size_t, void *); + + /* + there is only one value currently used: 192 (GROUP_AUDIO_PACKET_ID) + no need to reserve 255 addition pointers to handlers never used + that is why irungentoo's array of pointers was replaced with only one ptr + if someone in future want to use another id of packet in addition to the 192 + just return back this array... or write better code + isotoxin.dev struct { - int (*function)(void *, uint32_t, uint32_t, void *, const uint8_t *, uint16_t); + int (*function)(void *, int, int, void *, const uint8_t *, uint16_t); } lossy_packethandlers[256]; + */ + + int(*lossy_packethandler)(void *, int, int, void *, const uint8_t *, uint16_t); } Group_Chats; + /* Set the callback for group invites. * - * Function(Group_Chats *g_c, uint32_t friendnumber, uint8_t type, uint8_t *data, uint16_t length, void *userdata) + * Function(Group_Chats *g_c, int32_t friendnumber, uint8_t type, uint8_t *data, uint16_t length, void *userdata) * * data of length is what needs to be passed to join_groupchat(). */ @@ -137,7 +168,7 @@ void g_callback_group_invite(Group_Chats *g_c, void (*function)(Messenger *m, ui /* Set the callback for group messages. * - * Function(Group_Chats *g_c, uint32_t groupnumber, uint32_t friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) + * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) */ void g_callback_group_message(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, uint32_t, int, const uint8_t *, size_t, void *)); @@ -145,7 +176,7 @@ void g_callback_group_message(Group_Chats *g_c, void (*function)(Messenger *m, u /* Set callback function for title changes. * - * Function(Group_Chats *g_c, uint32_t groupnumber, uint32_t friendgroupnumber, uint8_t * title, uint8_t length, void *userdata) + * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * title, uint8_t length, void *userdata) * if friendgroupnumber == -1, then author is unknown (e.g. initial joining the group) */ void g_callback_group_title(Group_Chats *g_c, void (*function)(Messenger *m, uint32_t, uint32_t, const uint8_t *, @@ -173,40 +204,43 @@ void g_callback_peer_list_changed(Group_Chats *g_c, void (*function)(Messenger * * return group number on success. * return -1 on failure. */ -int add_groupchat(Group_Chats *g_c, uint8_t type); +int add_groupchat(Group_Chats *g_c, uint8_t type, const uint8_t *uid /*can be NULL*/); /* Delete a groupchat from the chats array. * * return 0 on success. * return -1 if groupnumber is invalid. */ -int del_groupchat(Group_Chats *g_c, uint32_t groupnumber); +int del_groupchat(Group_Chats *g_c, int groupnumber); + +int enter_conference(Group_Chats *g_c, int groupnumber); +int leave_conference(Group_Chats *g_c, int groupnumber, bool keep_leave); -/* Copy the public key of peernumber who is in groupnumber to pk. +/* Copy the public key of peer_index who is in groupnumber to pk. * pk must be CRYPTO_PUBLIC_KEY_SIZE long. * * return 0 on success * return -1 if groupnumber is invalid. - * return -2 if peernumber is invalid. + * return -2 if peer_index is invalid. */ -int group_peer_pubkey(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, uint8_t *pk); +int group_peer_pubkey(const Group_Chats *g_c, int groupnumber, int peer_index, uint8_t *pk); /* - * Return the size of peernumber's name. + * Return the size of peer_index's name. * * return -1 if groupnumber is invalid. - * return -2 if peernumber is invalid. + * return -2 if peer_index is invalid. */ -int group_peername_size(const Group_Chats *g_c, uint32_t groupnumber, int32_t peernumber); +int group_peername_size(const Group_Chats *g_c, int groupnumber, int peer_index); -/* Copy the name of peernumber who is in groupnumber to name. +/* Copy the name of peer_index who is in groupnumber to name. * name must be at least MAX_NAME_LENGTH long. * * return length of name if success * return -1 if groupnumber is invalid. - * return -2 if peernumber is invalid. + * return -2 if peer_index is invalid. */ -int group_peername(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, uint8_t *name); +int group_peername(const Group_Chats *g_c, int groupnumber, int peer_index, uint8_t *name); /* invite friendnumber to groupnumber * @@ -214,7 +248,7 @@ int group_peername(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, * return -1 if groupnumber is invalid. * return -2 if invite packet failed to send. */ -int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber); +int invite_friend(Group_Chats *g_c, int32_t friendnumber, int groupnumber); /* Join a group (you need to have been invited first.) * @@ -228,20 +262,19 @@ int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) * return -5 if group instance failed to initialize. * return -6 if join packet fails to send. */ -int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_type, const uint8_t *data, - uint16_t length); +int join_groupchat(Group_Chats *g_c, int32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length); /* send a group message * return 0 on success * see: send_message_group() for error codes. */ -int group_message_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *message, uint16_t length); +int group_message_send(const Group_Chats *g_c, int groupnumber, const uint8_t *message, uint16_t length); /* send a group action * return 0 on success * see: send_message_group() for error codes. */ -int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *action, uint16_t length); +int group_action_send(const Group_Chats *g_c, int groupnumber, const uint8_t *action, uint16_t length); /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success @@ -249,14 +282,14 @@ int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_ * return -2 if title is too long or empty. * return -3 if packet fails to send. */ -int group_title_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *title, uint8_t title_len); +int group_title_send(const Group_Chats *g_c, int groupnumber, const uint8_t *title, uint8_t title_len); /* return the group's title size. * return -1 of groupnumber is invalid. * return -2 if title is too long or empty. */ -int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber); +int group_title_get_size(const Group_Chats *g_c, int groupnumber); /* Get group title from groupnumber and put it in title. * Title needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. @@ -265,20 +298,20 @@ int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber); * return -1 if groupnumber is invalid. * return -2 if title is too long or empty. */ -int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title); +int group_title_get(const Group_Chats *g_c, int groupnumber, uint8_t *title); /* Return the number of peers in the group chat on success. * return -1 if groupnumber is invalid. */ -int group_number_peers(const Group_Chats *g_c, uint32_t groupnumber); +int group_number_peers(const Group_Chats *g_c, int groupnumber); -/* return 1 if the peernumber corresponds to ours. - * return 0 if the peernumber is not ours. +/* return 1 if the peer_index corresponds to ours. + * return 0 if the peer_index is not ours. * return -1 if groupnumber is invalid. - * return -2 if peernumber is invalid. + * return -2 if peer_index is invalid. * return -3 if we are not connected to the group chat. */ -int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, int peernumber); +int group_peer_index_is_ours(const Group_Chats *g_c, int groupnumber, int peer_index); /* List all the peers in the group chat. * @@ -290,17 +323,16 @@ int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, int p * * return -1 on failure. */ -int group_names(const Group_Chats *g_c, uint32_t groupnumber, uint8_t names[][MAX_NAME_LENGTH], uint16_t lengths[], +int group_names(const Group_Chats *g_c, int groupnumber, uint8_t names[][MAX_NAME_LENGTH], uint16_t lengths[], uint16_t length); /* Set handlers for custom lossy packets. * * NOTE: Handler must return 0 if packet is to be relayed, -1 if the packet should not be relayed. * - * Function(void *group object (set with group_set_object), uint32_t groupnumber, uint32_t friendgroupnumber, void *group peer object (set with group_peer_set_object), const uint8_t *packet, uint16_t length) + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object), const uint8_t *packet, uint16_t length) */ -void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*function)(void *, uint32_t, uint32_t, - void *, +void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*function)(void *, int, int, void *, const uint8_t *, uint16_t)); /* High level function to send custom lossy packets. @@ -308,27 +340,36 @@ void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*fu * return -1 on failure. * return 0 on success. */ -int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length); +int send_group_lossy_packet(const Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length); /* Return the number of chats in the instance m. * You should use this to determine how much memory to allocate * for copy_chatlist. */ -uint32_t count_chatlist(Group_Chats *g_c); +uint32_t count_chatlist(const Group_Chats *g_c); /* Copy a list of valid chat IDs into the array out_list. * If out_list is NULL, returns 0. * Otherwise, returns the number of elements copied. * If the array was too small, the contents * of out_list will be truncated to list_size. */ -uint32_t copy_chatlist(Group_Chats *g_c, uint32_t *out_list, uint32_t list_size); +uint32_t copy_chatlist(const Group_Chats *g_c, uint32_t *out_list, uint32_t list_size); /* return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. * * return -1 on failure. * return type on success. */ -int group_get_type(const Group_Chats *g_c, uint32_t groupnumber); +int group_get_type(const Group_Chats *g_c, int groupnumber); + +/* Copies the unique id of group_chat[groupnumber] into uid. +* +* return false on failure. +* return true on success. +*/ +bool conference_get_id(const Group_Chats *g_c, int groupnumber, uint8_t *uid); + +int conference_by_uid(const Group_Chats *g_c, const uint8_t *uid); /* Send current name (set in messenger) to all online groups. */ @@ -339,28 +380,28 @@ void send_name_all_groups(Group_Chats *g_c); * return 0 on success. * return -1 on failure */ -int group_set_object(const Group_Chats *g_c, uint32_t groupnumber, void *object); +int group_set_object(const Group_Chats *g_c, int groupnumber, void *object); /* Set the object that is tied to the group peer. * * return 0 on success. * return -1 on failure */ -int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, int peernumber, void *object); +int group_peer_set_object(const Group_Chats *g_c, int groupnumber, int peernumber, void *object); /* Return the object tide to the group chat previously set by group_set_object. * * return NULL on failure. * return object on success. */ -void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber); +void *group_get_object(const Group_Chats *g_c, int groupnumber); /* Return the object tide to the group chat peer previously set by group_peer_set_object. * * return NULL on failure. * return object on success. */ -void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, int peernumber); +void *group_peer_get_object(const Group_Chats *g_c, int groupnumber, int peernumber); /* Set a function to be called when a new peer joins a group chat. * @@ -369,27 +410,25 @@ void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, int pe * return 0 on success. * return -1 on failure. */ -int callback_groupchat_peer_new(const Group_Chats *g_c, uint32_t groupnumber, void (*function)(void *, uint32_t, - uint32_t)); +int callback_groupchat_peer_new(const Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int)); /* Set a function to be called when a peer leaves a group chat. * - * Function(void *group object (set with group_set_object), uint32_t groupnumber, uint32_t friendgroupnumber, void *group peer object (set with group_peer_set_object)) + * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object)) * * return 0 on success. * return -1 on failure. */ -int callback_groupchat_peer_delete(Group_Chats *g_c, uint32_t groupnumber, void (*function)(void *, uint32_t, uint32_t, - void *)); +int callback_groupchat_peer_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int, void *)); /* Set a function to be called when the group chat is deleted. * - * Function(void *group object (set with group_set_object), uint32_t groupnumber) + * Function(void *group object (set with group_set_object), int groupnumber) * * return 0 on success. * return -1 on failure. */ -int callback_groupchat_delete(Group_Chats *g_c, uint32_t groupnumber, void (*function)(void *, uint32_t)); +int callback_groupchat_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int)); /* Create new groupchat instance. */ Group_Chats *new_groupchats(Messenger *m); diff --git a/toxcore/tox.api.h b/toxcore/tox.api.h index 59af69b9c3..cb3e41a146 100644 --- a/toxcore/tox.api.h +++ b/toxcore/tox.api.h @@ -240,6 +240,11 @@ const PUBLIC_KEY_SIZE = 32; */ const SECRET_KEY_SIZE = 32; +/** + * The size of a Tox Conference unique id in bytes. + */ +const CONFERENCE_UID_SIZE = 32; + /** * The size of the nospam in bytes when written in a Tox address. */ @@ -2089,6 +2094,12 @@ namespace conference { * or exits the conference. * * @param friend_number The friend who invited us. + * if friend_number == UINT32_MAX then conference automatically joined. + * On auto-join client must call tox_conference_join or toxav_join_av_groupchat + * immediately in callback. For api compatibility reason, if client don't + * call one these functions, conference will be deleted and toxcore + * totally forget this conference. + * * @param type The conference type (text only or audio/video). * @param cookie A piece of data of variable length required to join the * conference. @@ -2158,7 +2169,6 @@ namespace conference { } - /** * Creates a new conference. * @@ -2187,6 +2197,52 @@ namespace conference { CONFERENCE_NOT_FOUND, } + /** + * This function starts entering process. + * Call this function only if you leave conference using $leave. + * No need to call this function for just created conferences + * + * @param conference_number The conference number of the conference to be entered. + * conference_number can be obtained by $by_uid + * + * @return true on success. + */ + bool enter(uint32_t conference_number) { + /** + * Conference already connected or enter process already started + */ + ALREADY, + /** + * The conference number passed did not designate a valid conference. + */ + NOT_FOUND, + } + + /** + * This function disconnects conference. + * Call this function to disconnect conference without delete. + * Even error TOX_ERR_CONFERENCE_LEAVE_ALREADY, new keep_leave flag will be applied to conference + * + * @param conference_number The conference number of the conference to be disconnected. + * conference_number can be obtained by $by_uid. + * + * @param keep_leave Set true to keep in leave state + * No one can invite you to this conference after you leave it with keep_leave is true. + * Also keep_leave == true means conference will not try to connect to other peers after restart. + * Call $enter to enable auto connect and invite. + * + * @return true on success. + */ + bool leave(uint32_t conference_number, bool keep_leave) { + /** + * Conference already disconnected + */ + ALREADY, + /** + * The conference number passed did not designate a valid conference. + */ + NOT_FOUND, + } namespace peer { @@ -2431,6 +2487,29 @@ namespace conference { } } + /** + * Get the conference unique ID. + * + * @param uid A memory region large enough to store $CONFERENCE_UID_SIZE bytes + * + * @return true on success. + */ + const bool get_uid(uint32_t conference_number, uint8_t[CONFERENCE_UID_SIZE] uid); + + /** + * Return the conference number associated with that uid. + * + * @return the conference number on success, UINT32_MAX on failure. + * @param uid A byte array containing the conference id ($CONFERENCE_UID_SIZE). + */ + const uint32_t by_uid(const uint8_t[CONFERENCE_UID_SIZE] uid) { + NULL, + /** + * No conference with the given uid exists on the conference list. + */ + NOT_FOUND, + } + } diff --git a/toxcore/tox.c b/toxcore/tox.c index 3db2bd35f3..185a7aa369 100644 --- a/toxcore/tox.c +++ b/toxcore/tox.c @@ -71,6 +71,9 @@ typedef struct Messenger Tox; #error TOX_MAX_STATUS_MESSAGE_LENGTH is assumed to be equal to MAX_STATUSMESSAGE_LENGTH #endif +#if TOX_CONFERENCE_UID_SIZE != (GROUP_IDENTIFIER_LENGTH - 1) +#error TOX_CONFERENCE_UID_SIZE is assumed to be equal to (GROUP_IDENTIFIER_LENGTH - 1) +#endif bool tox_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) { @@ -1119,7 +1122,7 @@ void tox_callback_conference_peer_list_changed(Tox *tox, tox_conference_peer_lis uint32_t tox_conference_new(Tox *tox, TOX_ERR_CONFERENCE_NEW *error) { Messenger *m = tox; - int ret = add_groupchat((Group_Chats *)m->conferences_object, GROUPCHAT_TYPE_TEXT); + int ret = add_groupchat((Group_Chats *)m->conferences_object, GROUPCHAT_TYPE_TEXT, nullptr); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_NEW_INIT); @@ -1144,6 +1147,44 @@ bool tox_conference_delete(Tox *tox, uint32_t conference_number, TOX_ERR_CONFERE return true; } +bool tox_conference_enter(Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_ENTER *error) +{ + Messenger *m = tox; + int ret = enter_conference((Group_Chats *)m->conferences_object, conference_number); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_ENTER_NOT_FOUND); + return false; + } + + if (ret == -2) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_ENTER_ALREADY); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_ENTER_OK); + return true; +} + +bool tox_conference_leave(Tox *tox, uint32_t conference_number, bool keep_leave, TOX_ERR_CONFERENCE_LEAVE *error) +{ + Messenger *m = tox; + int ret = leave_conference((Group_Chats *)m->conferences_object, conference_number, keep_leave); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_LEAVE_NOT_FOUND); + return false; + } + + if (ret == -2) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_LEAVE_ALREADY); + return false; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_LEAVE_OK); + return true; +} + uint32_t tox_conference_peer_count(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_PEER_QUERY *error) { const Messenger *m = tox; @@ -1222,7 +1263,7 @@ bool tox_conference_peer_number_is_ours(const Tox *tox, uint32_t conference_numb TOX_ERR_CONFERENCE_PEER_QUERY *error) { const Messenger *m = tox; - int ret = group_peernumber_is_ours((Group_Chats *)m->conferences_object, conference_number, peer_number); + int ret = group_peer_index_is_ours((Group_Chats *)m->conferences_object, conference_number, peer_number); switch (ret) { case -1: @@ -1423,6 +1464,32 @@ TOX_CONFERENCE_TYPE tox_conference_get_type(const Tox *tox, uint32_t conference_ return (TOX_CONFERENCE_TYPE)ret; } +bool tox_conference_get_uid(const Tox *tox, uint32_t conference_number, uint8_t *uid /* TOX_CONFERENCE_ID_SIZE bytes */) +{ + const Messenger *m = tox; + return conference_get_id((Group_Chats *)m->conferences_object, conference_number, uid); +} + +uint32_t tox_conference_by_uid(const Tox *tox, const uint8_t *uid, TOX_ERR_CONFERENCE_BY_UID *error) +{ + if (!uid) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_NULL); + return UINT32_MAX; + } + + const Messenger *m = tox; + int32_t ret = conference_by_uid((Group_Chats *)m->conferences_object, uid); + + if (ret == -1) { + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_NOT_FOUND); + return UINT32_MAX; + } + + SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_OK); + return ret; +} + + static void set_custom_packet_error(int ret, TOX_ERR_FRIEND_CUSTOM_PACKET *error) { switch (ret) { diff --git a/toxcore/tox.h b/toxcore/tox.h index 41a0994ed6..9dac2bb649 100644 --- a/toxcore/tox.h +++ b/toxcore/tox.h @@ -245,6 +245,13 @@ uint32_t tox_public_key_size(void); uint32_t tox_secret_key_size(void); +/** + * The size of a Tox Conference unique id in bytes. + */ +#define TOX_CONFERENCE_UID_SIZE 32 + +uint32_t tox_conference_uid_size(void); + /** * The size of the nospam in bytes when written in a Tox address. */ @@ -2358,6 +2365,12 @@ typedef enum TOX_CONFERENCE_TYPE { * or exits the conference. * * @param friend_number The friend who invited us. + * if friend_number == UINT32_MAX then conference automatically joined. + * On auto-join client must call tox_conference_join or toxav_join_av_groupchat + * immediately in callback. For api compatibility reason, if client don't + * call one these functions, conference will be deleted and toxcore + * totally forget this conference. + * * @param type The conference type (text only or audio/video). * @param cookie A piece of data of variable length required to join the * conference. @@ -2491,6 +2504,75 @@ typedef enum TOX_ERR_CONFERENCE_DELETE { */ bool tox_conference_delete(Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_DELETE *error); +typedef enum TOX_ERR_CONFERENCE_ENTER { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_ENTER_OK, + + /** + * Conference already connected or enter process already started + */ + TOX_ERR_CONFERENCE_ENTER_ALREADY, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_ENTER_NOT_FOUND, + +} TOX_ERR_CONFERENCE_ENTER; + + +/** + * This function starts entering process. + * Call this function only if you leave conference using tox_conference_leave. + * No need to call this function for just created conferences + * + * @param conference_number The conference number of the conference to be entered. + * conference_number can be obtained by tox_conference_by_uid + * + * @return true on success. + */ +bool tox_conference_enter(Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_ENTER *error); + +typedef enum TOX_ERR_CONFERENCE_LEAVE { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_LEAVE_OK, + + /** + * Conference already disconnected + */ + TOX_ERR_CONFERENCE_LEAVE_ALREADY, + + /** + * The conference number passed did not designate a valid conference. + */ + TOX_ERR_CONFERENCE_LEAVE_NOT_FOUND, + +} TOX_ERR_CONFERENCE_LEAVE; + + +/** + * This function disconnects conference. + * Call this function to disconnect conference without delete. + * Even error TOX_ERR_CONFERENCE_LEAVE_ALREADY, new keep_leave flag will be applied to conference + * + * @param conference_number The conference number of the conference to be disconnected. + * conference_number can be obtained by tox_conference_by_uid. + * + * @param keep_leave Set true to keep in leave state + * No one can invite you to this conference after you leave it with keep_leave is true. + * Also keep_leave == true means conference will not try to connect to other peers after restart. + * Call tox_conference_enter to enable auto connect and invite. + * + * @return true on success. + */ +bool tox_conference_leave(Tox *tox, uint32_t conference_number, bool keep_leave, TOX_ERR_CONFERENCE_LEAVE *error); + /** * Error codes for peer info queries. */ @@ -2781,6 +2863,43 @@ typedef enum TOX_ERR_CONFERENCE_GET_TYPE { TOX_CONFERENCE_TYPE tox_conference_get_type(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_GET_TYPE *error); +/** + * Get the conference unique ID. + * + * @param uid A memory region large enough to store TOX_CONFERENCE_UID_SIZE bytes + * + * @return true on success. + */ +bool tox_conference_get_uid(const Tox *tox, uint32_t conference_number, uint8_t *uid); + +typedef enum TOX_ERR_CONFERENCE_BY_UID { + + /** + * The function returned successfully. + */ + TOX_ERR_CONFERENCE_BY_UID_OK, + + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOX_ERR_CONFERENCE_BY_UID_NULL, + + /** + * No conference with the given uid exists on the conference list. + */ + TOX_ERR_CONFERENCE_BY_UID_NOT_FOUND, + +} TOX_ERR_CONFERENCE_BY_UID; + + +/** + * Return the conference number associated with that uid. + * + * @return the conference number on success, UINT32_MAX on failure. + * @param uid A byte array containing the conference id (TOX_CONFERENCE_UID_SIZE). + */ +uint32_t tox_conference_by_uid(const Tox *tox, const uint8_t *uid, TOX_ERR_CONFERENCE_BY_UID *error); + /******************************************************************************* *