261d2e53b7
11ab1d2a723 fix: reduce memory usage in group chats by 75% Significantly reduced the memory usage of groups since all message slots are preallocated for every peer for send and receive buffers of buffer size (hundreds of MiB peak when save contained alot of peers to try to connect to) 4f09f4e147c chore: Fix tsan build by moving it to GitHub CI. 6460c25c9e0 refactor: Use `merge_sort` instead of `qsort` for sorting. c660bbe8c95 test: Fix crypto_test to initialise its plain text buffer. 0204db6184b cleanup: Fix layering check warnings. df2211e1548 refactor: Use tox memory allocator for temporary buffers in crypto. ac812871a2e feat: implement the last 2 missing network struct functions and make use of them 29d1043be0b test: friend request test now tests min/max message sizes 93aafd78c1f fix: friend requests with very long messages are no longer dropped 819aa2b2618 feat: Add option to disable DNS lookups in toxcore. 0ac23cee035 fix: windows use of REUSEADDR 7d2811d302d chore(ci): make bazel server shutdown faster 1dc399ba20d chore: Use vcpkg instead of conan in the MSVC build. 14d823165d9 chore: Migrate to conan 2. bdd17c16787 cleanup: Allocate logger using tox memory allocator. b396c061515 chore(deps): bump third_party/cmp from `2ac6bca` to `52bfcfa` 2e94da60d09 feat(net): add missing connect to network struct 41fb1839c7b chore: Add check to ensure version numbers agree. 934a8301113 chore: Release 0.2.20 3acef4bf044 fix: Add missing free in dht_get_nodes_response event. git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: 11ab1d2a7232eee19b51ce126ccce267d6578903
3924 lines
107 KiB
C
3924 lines
107 KiB
C
/* SPDX-License-Identifier: GPL-3.0-or-later
|
|
* Copyright © 2016-2018 The TokTok team.
|
|
* Copyright © 2014 Tox project.
|
|
*/
|
|
|
|
/**
|
|
* Slightly better groupchats implementation.
|
|
*/
|
|
#include "group.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h> // calloc, free
|
|
#include <string.h>
|
|
|
|
#include "DHT.h"
|
|
#include "Messenger.h"
|
|
#include "attributes.h"
|
|
#include "ccompat.h"
|
|
#include "crypto_core.h"
|
|
#include "friend_connection.h"
|
|
#include "group_common.h"
|
|
#include "logger.h"
|
|
#include "mem.h"
|
|
#include "mono_time.h"
|
|
#include "net_crypto.h"
|
|
#include "network.h"
|
|
#include "sort.h"
|
|
#include "state.h"
|
|
#include "util.h"
|
|
|
|
enum {
|
|
/** Connection is to one of the closest DESIRED_CLOSEST peers */
|
|
GROUPCHAT_CONNECTION_REASON_CLOSEST = 1 << 0,
|
|
|
|
/** Connection is to a peer we are introducing to the conference */
|
|
GROUPCHAT_CONNECTION_REASON_INTRODUCING = 1 << 1,
|
|
|
|
/** Connection is to a peer who is introducing us to the conference */
|
|
GROUPCHAT_CONNECTION_REASON_INTRODUCER = 1 << 2,
|
|
};
|
|
|
|
typedef enum Groupchat_Connection_Type {
|
|
GROUPCHAT_CONNECTION_NONE,
|
|
GROUPCHAT_CONNECTION_CONNECTING,
|
|
GROUPCHAT_CONNECTION_ONLINE,
|
|
} Groupchat_Connection_Type;
|
|
|
|
typedef enum Groupchat_Status {
|
|
GROUPCHAT_STATUS_NONE,
|
|
GROUPCHAT_STATUS_VALID,
|
|
GROUPCHAT_STATUS_CONNECTED,
|
|
} Groupchat_Status;
|
|
|
|
#define GROUP_ID_LENGTH CRYPTO_SYMMETRIC_KEY_SIZE
|
|
|
|
#define DESIRED_CLOSEST 4
|
|
#define MAX_GROUP_CONNECTIONS 16
|
|
#define MAX_LAST_MESSAGE_INFOS 8
|
|
#define MAX_LOSSY_COUNT 256
|
|
|
|
/** Maximum number of frozen peers to store; `group_set_max_frozen()` overrides. */
|
|
#define MAX_FROZEN_DEFAULT 128
|
|
|
|
typedef struct Message_Info {
|
|
uint32_t message_number;
|
|
uint8_t message_id;
|
|
} Message_Info;
|
|
|
|
typedef struct Group_Peer {
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
bool temp_pk_updated;
|
|
bool is_friend;
|
|
|
|
uint64_t last_active;
|
|
|
|
Message_Info
|
|
last_message_infos[MAX_LAST_MESSAGE_INFOS]; /* received messages, strictly decreasing in message_number */
|
|
uint8_t num_last_message_infos;
|
|
|
|
uint8_t nick[MAX_NAME_LENGTH];
|
|
uint8_t nick_len;
|
|
bool nick_updated;
|
|
|
|
uint16_t peer_number;
|
|
|
|
uint8_t recv_lossy[MAX_LOSSY_COUNT];
|
|
uint16_t bottom_lossy_number;
|
|
uint16_t top_lossy_number;
|
|
|
|
void *object;
|
|
} Group_Peer;
|
|
|
|
typedef struct Groupchat_Connection {
|
|
uint8_t type; /* `GROUPCHAT_CONNECTION_*` */
|
|
uint8_t reasons; /* bit field with flags `GROUPCHAT_CONNECTION_REASON_*` */
|
|
uint32_t number;
|
|
uint16_t group_number;
|
|
} Groupchat_Connection;
|
|
|
|
typedef struct Groupchat_Closest {
|
|
/**
|
|
* Whether this peer is active in the closest_peers array.
|
|
*/
|
|
bool active;
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
} Groupchat_Closest;
|
|
|
|
typedef struct Group_c {
|
|
uint8_t status;
|
|
|
|
bool need_send_name;
|
|
bool title_fresh;
|
|
|
|
Group_Peer *group;
|
|
uint32_t numpeers;
|
|
|
|
Group_Peer *frozen;
|
|
uint32_t numfrozen;
|
|
|
|
uint32_t maxfrozen;
|
|
|
|
Groupchat_Connection connections[MAX_GROUP_CONNECTIONS];
|
|
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
Groupchat_Closest closest_peers[DESIRED_CLOSEST];
|
|
uint8_t changed;
|
|
|
|
uint8_t type;
|
|
uint8_t id[GROUP_ID_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;
|
|
|
|
uint32_t num_introducer_connections;
|
|
|
|
void *object;
|
|
|
|
peer_on_join_cb *peer_on_join;
|
|
peer_on_leave_cb *peer_on_leave;
|
|
group_on_delete_cb *group_on_delete;
|
|
} Group_c;
|
|
|
|
struct Group_Chats {
|
|
const Mono_Time *mono_time;
|
|
|
|
Messenger *m;
|
|
Friend_Connections *fr_c;
|
|
|
|
Group_c *chats;
|
|
uint16_t num_chats;
|
|
|
|
g_conference_invite_cb *invite_callback;
|
|
g_conference_connected_cb *connected_callback;
|
|
g_conference_message_cb *message_callback;
|
|
peer_name_cb *peer_name_callback;
|
|
peer_list_changed_cb *peer_list_changed_callback;
|
|
title_cb *title_callback;
|
|
|
|
lossy_packet_cb *lossy_packethandlers[256];
|
|
};
|
|
|
|
static const Group_c empty_group_c = {0};
|
|
static const Group_Peer empty_group_peer = {{0}};
|
|
|
|
/**
|
|
* Packet type IDs as per the protocol specification.
|
|
*/
|
|
typedef enum Group_Message_Id {
|
|
GROUP_MESSAGE_PING_ID = 0,
|
|
GROUP_MESSAGE_NEW_PEER_ID = 16,
|
|
GROUP_MESSAGE_KILL_PEER_ID = 17,
|
|
GROUP_MESSAGE_FREEZE_PEER_ID = 18,
|
|
GROUP_MESSAGE_NAME_ID = 48,
|
|
GROUP_MESSAGE_TITLE_ID = 49,
|
|
} Group_Message_Id;
|
|
|
|
#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))
|
|
|
|
typedef enum Invite_Id {
|
|
INVITE_ID = 0,
|
|
INVITE_ACCEPT_ID = 1,
|
|
INVITE_MEMBER_ID = 2,
|
|
} Invite_Id;
|
|
|
|
#define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + 1 + GROUP_ID_LENGTH)
|
|
#define INVITE_ACCEPT_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH)
|
|
#define INVITE_MEMBER_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH + sizeof(uint16_t))
|
|
|
|
#define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + 1 + GROUP_ID_LENGTH)
|
|
|
|
typedef enum Peer_Id {
|
|
PEER_INTRODUCED_ID = 1,
|
|
PEER_QUERY_ID = 8,
|
|
PEER_RESPONSE_ID = 9,
|
|
PEER_TITLE_ID = 10,
|
|
} Peer_Id;
|
|
|
|
#define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1)
|
|
|
|
static_assert(GROUP_ID_LENGTH == CRYPTO_PUBLIC_KEY_SIZE,
|
|
"GROUP_ID_LENGTH should be equal to CRYPTO_PUBLIC_KEY_SIZE");
|
|
|
|
const Mono_Time *g_mono_time(const Group_Chats *g_c)
|
|
{
|
|
return g_c->mono_time;
|
|
}
|
|
|
|
non_null()
|
|
static bool group_id_eq(const uint8_t *a, const uint8_t *b)
|
|
{
|
|
return pk_equal(a, b);
|
|
}
|
|
|
|
non_null()
|
|
static bool g_title_eq(const Group_c *g, const uint8_t *title, uint8_t title_len)
|
|
{
|
|
return memeq(g->title, g->title_len, title, title_len);
|
|
}
|
|
|
|
non_null()
|
|
static bool g_peer_nick_eq(const Group_Peer *peer, const uint8_t *nick, uint8_t nick_len)
|
|
{
|
|
return memeq(peer->nick, peer->nick_len, nick, nick_len);
|
|
}
|
|
|
|
/**
|
|
* @retval false if the groupnumber is not valid.
|
|
* @retval true if the groupnumber is valid.
|
|
*/
|
|
non_null()
|
|
static bool is_groupnumber_valid(const Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
return groupnumber < g_c->num_chats
|
|
&& g_c->chats != nullptr
|
|
&& g_c->chats[groupnumber].status != GROUPCHAT_STATUS_NONE;
|
|
}
|
|
|
|
/** @brief Set the size of the groupchat list to num.
|
|
*
|
|
* @retval false if realloc fails.
|
|
* @retval true if it succeeds.
|
|
*/
|
|
non_null()
|
|
static bool realloc_conferences(Group_Chats *g_c, uint16_t num)
|
|
{
|
|
if (num == 0) {
|
|
free(g_c->chats);
|
|
g_c->chats = nullptr;
|
|
return true;
|
|
}
|
|
|
|
Group_c *newgroup_chats = (Group_c *)realloc(g_c->chats, num * sizeof(Group_c));
|
|
|
|
if (newgroup_chats == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
g_c->chats = newgroup_chats;
|
|
return true;
|
|
}
|
|
|
|
non_null()
|
|
static void setup_conference(Group_c *g)
|
|
{
|
|
*g = empty_group_c;
|
|
g->maxfrozen = MAX_FROZEN_DEFAULT;
|
|
}
|
|
|
|
/** @brief Create a new empty groupchat connection.
|
|
*
|
|
* @retval -1 on failure.
|
|
* @return groupnumber on success.
|
|
*/
|
|
non_null()
|
|
static int32_t create_group_chat(Group_Chats *g_c)
|
|
{
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
if (g_c->chats[i].status == GROUPCHAT_STATUS_NONE) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
if (realloc_conferences(g_c, g_c->num_chats + 1)) {
|
|
const uint16_t id = g_c->num_chats;
|
|
++g_c->num_chats;
|
|
setup_conference(&g_c->chats[id]);
|
|
return id;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
non_null()
|
|
static void wipe_group_c(Group_c *g)
|
|
{
|
|
free(g->frozen);
|
|
free(g->group);
|
|
crypto_memzero(g, sizeof(Group_c));
|
|
}
|
|
|
|
/** @brief Wipe a groupchat.
|
|
*
|
|
* @retval true on success.
|
|
*/
|
|
non_null()
|
|
static bool wipe_group_chat(Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
if (groupnumber >= g_c->num_chats || g_c->chats == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
wipe_group_c(&g_c->chats[groupnumber]);
|
|
|
|
uint16_t i;
|
|
|
|
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_conferences(g_c, g_c->num_chats);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
non_null()
|
|
static Group_c *get_group_c(const Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
if (!is_groupnumber_valid(g_c, groupnumber)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return &g_c->chats[groupnumber];
|
|
}
|
|
|
|
/**
|
|
* check if peer with real_pk is in peer array.
|
|
*
|
|
* @return peer index if peer is in group.
|
|
* @retval -1 if peer is not in group.
|
|
*
|
|
* TODO(irungentoo): make this more efficient.
|
|
*/
|
|
non_null()
|
|
static int peer_in_group(const Group_c *g, const uint8_t *real_pk)
|
|
{
|
|
for (uint32_t i = 0; i < g->numpeers; ++i) {
|
|
if (pk_equal(g->group[i].real_pk, real_pk)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
non_null()
|
|
static int frozen_in_group(const Group_c *g, const uint8_t *real_pk)
|
|
{
|
|
for (uint32_t i = 0; i < g->numfrozen; ++i) {
|
|
if (pk_equal(g->frozen[i].real_pk, real_pk)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* check if group with the given type and id is in group array.
|
|
*
|
|
* @return group number if peer is in list.
|
|
* @retval -1 if group is not in list.
|
|
*
|
|
* TODO(irungentoo): make this more efficient and maybe use constant time comparisons?
|
|
*/
|
|
non_null()
|
|
static int32_t get_group_num(const Group_Chats *g_c, const uint8_t type, const uint8_t *id)
|
|
{
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
if (g_c->chats[i].type == type && group_id_eq(g_c->chats[i].id, id)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int32_t conference_by_id(const Group_Chats *g_c, const uint8_t *id)
|
|
{
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
if (group_id_eq(g_c->chats[i].id, id)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* check if peer with peer_number is in peer array.
|
|
*
|
|
* @return peer index if peer is in chat.
|
|
* @retval -1 if peer is not in chat.
|
|
*
|
|
* TODO(irungentoo): make this more efficient.
|
|
*/
|
|
non_null()
|
|
static int get_peer_index(const Group_c *g, uint16_t peer_number)
|
|
{
|
|
for (uint32_t i = 0; i < g->numpeers; ++i) {
|
|
if (g->group[i].peer_number == peer_number) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
non_null()
|
|
static uint64_t calculate_comp_value(const uint8_t *pk1, const uint8_t *pk2)
|
|
{
|
|
uint64_t cmp1 = 0;
|
|
uint64_t cmp2 = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
typedef enum Groupchat_Closest_Change {
|
|
GROUPCHAT_CLOSEST_CHANGE_NONE,
|
|
GROUPCHAT_CLOSEST_CHANGE_ADDED,
|
|
GROUPCHAT_CLOSEST_CHANGE_REMOVED,
|
|
} Groupchat_Closest_Change;
|
|
|
|
non_null()
|
|
static bool add_to_closest(Group_c *g, const uint8_t *real_pk, const uint8_t *temp_pk)
|
|
{
|
|
if (pk_equal(g->real_pk, real_pk)) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int index = DESIRED_CLOSEST;
|
|
|
|
for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) {
|
|
if (g->closest_peers[i].active && pk_equal(real_pk, g->closest_peers[i].real_pk)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) {
|
|
if (!g->closest_peers[i].active) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index == DESIRED_CLOSEST) {
|
|
uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk);
|
|
uint64_t comp_d = 0;
|
|
|
|
for (unsigned int i = 0; i < (DESIRED_CLOSEST / 2); ++i) {
|
|
const uint64_t comp = calculate_comp_value(g->real_pk, g->closest_peers[i].real_pk);
|
|
|
|
if (comp > comp_val && comp > comp_d) {
|
|
index = i;
|
|
comp_d = comp;
|
|
}
|
|
}
|
|
|
|
comp_val = calculate_comp_value(real_pk, g->real_pk);
|
|
|
|
for (unsigned int i = DESIRED_CLOSEST / 2; i < DESIRED_CLOSEST; ++i) {
|
|
const uint64_t comp = calculate_comp_value(g->closest_peers[i].real_pk, g->real_pk);
|
|
|
|
if (comp > comp_val && comp > comp_d) {
|
|
index = i;
|
|
comp_d = comp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (index == DESIRED_CLOSEST) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t old_real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
uint8_t old_temp_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
bool old = false;
|
|
|
|
if (g->closest_peers[index].active) {
|
|
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 = true;
|
|
}
|
|
|
|
g->closest_peers[index].active = true;
|
|
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);
|
|
|
|
if (old) {
|
|
add_to_closest(g, old_real_pk, old_temp_pk);
|
|
}
|
|
|
|
if (g->changed == GROUPCHAT_CLOSEST_CHANGE_NONE) {
|
|
g->changed = GROUPCHAT_CLOSEST_CHANGE_ADDED;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
non_null()
|
|
static bool pk_in_closest_peers(const Group_c *g, const uint8_t *real_pk)
|
|
{
|
|
for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) {
|
|
if (!g->closest_peers[i].active) {
|
|
continue;
|
|
}
|
|
|
|
if (pk_equal(g->closest_peers[i].real_pk, real_pk)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
non_null()
|
|
static void remove_connection_reason(Group_Chats *g_c, Group_c *g, uint16_t i, uint8_t reason);
|
|
|
|
non_null()
|
|
static void purge_closest(Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) {
|
|
continue;
|
|
}
|
|
|
|
if ((g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) == 0) {
|
|
continue;
|
|
}
|
|
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[i].number);
|
|
|
|
if (!pk_in_closest_peers(g, real_pk)) {
|
|
remove_connection_reason(g_c, g, i, GROUPCHAT_CONNECTION_REASON_CLOSEST);
|
|
}
|
|
}
|
|
}
|
|
|
|
non_null()
|
|
static bool send_packet_online(const Friend_Connections *fr_c, int friendcon_id, uint16_t group_num,
|
|
uint8_t type, const uint8_t *id);
|
|
|
|
non_null()
|
|
static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, Group_c *g, uint8_t reason,
|
|
bool lock);
|
|
|
|
non_null(1) nullable(3)
|
|
static void add_closest_connections(Group_Chats *g_c, uint32_t groupnumber, void *userdata)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < DESIRED_CLOSEST; ++i) {
|
|
if (!g->closest_peers[i].active) {
|
|
continue;
|
|
}
|
|
|
|
int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->closest_peers[i].real_pk);
|
|
|
|
bool fresh = false;
|
|
|
|
if (friendcon_id == -1) {
|
|
friendcon_id = new_friend_connection(g_c->fr_c, g->closest_peers[i].real_pk);
|
|
fresh = true;
|
|
|
|
if (friendcon_id == -1) {
|
|
continue;
|
|
}
|
|
|
|
set_dht_temp_pk(g_c->fr_c, friendcon_id, g->closest_peers[i].temp_pk, userdata);
|
|
}
|
|
|
|
const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g,
|
|
GROUPCHAT_CONNECTION_REASON_CLOSEST, !fresh);
|
|
|
|
if (connection_index == -1) {
|
|
if (fresh) {
|
|
kill_friend_connection(g_c->fr_c, friendcon_id);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED
|
|
&& g->connections[connection_index].type == GROUPCHAT_CONNECTION_CONNECTING) {
|
|
send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id);
|
|
}
|
|
}
|
|
}
|
|
|
|
non_null(1) nullable(3)
|
|
static bool connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *userdata)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (g->changed == GROUPCHAT_CLOSEST_CHANGE_NONE) {
|
|
return true;
|
|
}
|
|
|
|
if (g->changed == GROUPCHAT_CLOSEST_CHANGE_REMOVED) {
|
|
for (uint32_t i = 0; i < g->numpeers; ++i) {
|
|
add_to_closest(g, g->group[i].real_pk, g->group[i].temp_pk);
|
|
}
|
|
}
|
|
|
|
purge_closest(g_c, groupnumber);
|
|
|
|
add_closest_connections(g_c, groupnumber, userdata);
|
|
|
|
g->changed = GROUPCHAT_CLOSEST_CHANGE_NONE;
|
|
|
|
return true;
|
|
}
|
|
|
|
non_null()
|
|
static int get_frozen_index(const Group_c *g, uint16_t peer_number)
|
|
{
|
|
for (uint32_t i = 0; i < g->numfrozen; ++i) {
|
|
if (g->frozen[i].peer_number == peer_number) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
non_null()
|
|
static bool delete_frozen(Group_c *g, uint32_t frozen_index)
|
|
{
|
|
if (frozen_index >= g->numfrozen) {
|
|
return false;
|
|
}
|
|
|
|
--g->numfrozen;
|
|
|
|
if (g->numfrozen == 0) {
|
|
free(g->frozen);
|
|
g->frozen = nullptr;
|
|
} else {
|
|
if (g->numfrozen != frozen_index) {
|
|
g->frozen[frozen_index] = g->frozen[g->numfrozen];
|
|
}
|
|
|
|
Group_Peer *const frozen_temp = (Group_Peer *)realloc(g->frozen, g->numfrozen * sizeof(Group_Peer));
|
|
|
|
if (frozen_temp == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
g->frozen = frozen_temp;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** @brief Update last_active timestamp on peer, and thaw the peer if it is frozen.
|
|
*
|
|
* @return peer index if peer is in the conference.
|
|
* @retval -1 otherwise, and on error.
|
|
*/
|
|
non_null(1) nullable(4)
|
|
static int note_peer_active(Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_number, void *userdata)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const int peer_index = get_peer_index(g, peer_number);
|
|
|
|
if (peer_index != -1) {
|
|
g->group[peer_index].last_active = mono_time_get(g_c->mono_time);
|
|
return peer_index;
|
|
}
|
|
|
|
const int frozen_index = get_frozen_index(g, peer_number);
|
|
|
|
if (frozen_index == -1) {
|
|
return -1;
|
|
}
|
|
|
|
/* Now thaw the peer */
|
|
|
|
Group_Peer *temp = (Group_Peer *)realloc(g->group, (g->numpeers + 1) * sizeof(Group_Peer));
|
|
|
|
if (temp == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const uint32_t thawed_index = g->numpeers;
|
|
|
|
g->group = temp;
|
|
g->group[thawed_index] = g->frozen[frozen_index];
|
|
g->group[thawed_index].temp_pk_updated = false;
|
|
g->group[thawed_index].last_active = mono_time_get(g_c->mono_time);
|
|
|
|
add_to_closest(g, g->group[thawed_index].real_pk, g->group[thawed_index].temp_pk);
|
|
|
|
++g->numpeers;
|
|
|
|
delete_frozen(g, frozen_index);
|
|
|
|
if (g_c->peer_list_changed_callback != nullptr) {
|
|
g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata);
|
|
}
|
|
|
|
if (g->peer_on_join != nullptr) {
|
|
g->peer_on_join(g->object, groupnumber, thawed_index);
|
|
}
|
|
|
|
g->need_send_name = true;
|
|
|
|
return thawed_index;
|
|
}
|
|
|
|
non_null(1) nullable(4)
|
|
static bool delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata);
|
|
|
|
non_null(1, 3) nullable(4)
|
|
static void delete_any_peer_with_pk(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk, void *userdata)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return;
|
|
}
|
|
|
|
const int peer_index = peer_in_group(g, real_pk);
|
|
|
|
if (peer_index >= 0) {
|
|
delpeer(g_c, groupnumber, peer_index, userdata);
|
|
}
|
|
|
|
const int frozen_index = frozen_in_group(g, real_pk);
|
|
|
|
if (frozen_index >= 0) {
|
|
delete_frozen(g, frozen_index);
|
|
}
|
|
}
|
|
|
|
/** @brief Add a peer to the group chat, or update an existing peer.
|
|
*
|
|
* fresh indicates whether we should consider this information on the peer to
|
|
* be current, and so should update temp_pk and consider the peer active.
|
|
*
|
|
* do_gc_callback indicates whether we want to trigger callbacks set by the client
|
|
* via the public API. This should be set to false if this function is called
|
|
* from outside of the `tox_iterate()` loop.
|
|
*
|
|
* @return peer_index if success or peer already in chat.
|
|
* @retval -1 if error.
|
|
*/
|
|
non_null(1, 3, 4) nullable(6)
|
|
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 fresh, bool do_gc_callback)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const int peer_index = fresh ?
|
|
note_peer_active(g_c, groupnumber, peer_number, userdata) :
|
|
get_peer_index(g, peer_number);
|
|
|
|
if (peer_index != -1) {
|
|
if (!pk_equal(g->group[peer_index].real_pk, real_pk)) {
|
|
LOGGER_ERROR(g_c->m->log, "peer public key is incorrect for peer %d", peer_number);
|
|
return -1;
|
|
}
|
|
|
|
if (fresh || !g->group[peer_index].temp_pk_updated) {
|
|
pk_copy(g->group[peer_index].temp_pk, temp_pk);
|
|
g->group[peer_index].temp_pk_updated = true;
|
|
}
|
|
|
|
return peer_index;
|
|
}
|
|
|
|
if (!fresh) {
|
|
const int frozen_index = get_frozen_index(g, peer_number);
|
|
|
|
if (frozen_index != -1) {
|
|
if (!pk_equal(g->frozen[frozen_index].real_pk, real_pk)) {
|
|
return -1;
|
|
}
|
|
|
|
pk_copy(g->frozen[frozen_index].temp_pk, temp_pk);
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
delete_any_peer_with_pk(g_c, groupnumber, real_pk, userdata);
|
|
|
|
Group_Peer *temp = (Group_Peer *)realloc(g->group, (g->numpeers + 1) * sizeof(Group_Peer));
|
|
|
|
if (temp == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
temp[g->numpeers] = empty_group_peer;
|
|
g->group = temp;
|
|
|
|
const uint32_t new_index = g->numpeers;
|
|
|
|
pk_copy(g->group[new_index].real_pk, real_pk);
|
|
pk_copy(g->group[new_index].temp_pk, temp_pk);
|
|
g->group[new_index].temp_pk_updated = true;
|
|
g->group[new_index].peer_number = peer_number;
|
|
g->group[new_index].last_active = mono_time_get(g_c->mono_time);
|
|
g->group[new_index].is_friend = getfriend_id(g_c->m, real_pk) != -1;
|
|
++g->numpeers;
|
|
|
|
add_to_closest(g, real_pk, temp_pk);
|
|
|
|
if (do_gc_callback && g_c->peer_list_changed_callback != nullptr) {
|
|
g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata);
|
|
}
|
|
|
|
if (g->peer_on_join != nullptr) {
|
|
g->peer_on_join(g->object, groupnumber, new_index);
|
|
}
|
|
|
|
return new_index;
|
|
}
|
|
|
|
non_null()
|
|
static void remove_connection(Group_Chats *g_c, Group_c *g, uint16_t i)
|
|
{
|
|
if ((g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER) != 0) {
|
|
--g->num_introducer_connections;
|
|
}
|
|
|
|
kill_friend_connection(g_c->fr_c, g->connections[i].number);
|
|
g->connections[i].type = GROUPCHAT_CONNECTION_NONE;
|
|
}
|
|
|
|
non_null()
|
|
static void remove_from_closest(Group_c *g, int peer_index)
|
|
{
|
|
for (uint32_t i = 0; i < DESIRED_CLOSEST; ++i) {
|
|
if (g->closest_peers[i].active
|
|
&& pk_equal(g->closest_peers[i].real_pk, g->group[peer_index].real_pk)) {
|
|
g->closest_peers[i].active = false;
|
|
g->changed = GROUPCHAT_CLOSEST_CHANGE_REMOVED;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a peer from the group chat.
|
|
*
|
|
* return true on success
|
|
*/
|
|
static bool delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
remove_from_closest(g, peer_index);
|
|
|
|
const int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->group[peer_index].real_pk);
|
|
|
|
if (friendcon_id != -1) {
|
|
for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) {
|
|
continue;
|
|
}
|
|
|
|
if (g->connections[i].number == (unsigned int)friendcon_id) {
|
|
remove_connection(g_c, g, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
--g->numpeers;
|
|
|
|
void *peer_object = g->group[peer_index].object;
|
|
|
|
if (g->numpeers == 0) {
|
|
free(g->group);
|
|
g->group = nullptr;
|
|
} else {
|
|
if (g->numpeers != (uint32_t)peer_index) {
|
|
g->group[peer_index] = g->group[g->numpeers];
|
|
}
|
|
|
|
Group_Peer *temp = (Group_Peer *)realloc(g->group, g->numpeers * sizeof(Group_Peer));
|
|
|
|
if (temp == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
g->group = temp;
|
|
}
|
|
|
|
if (g_c->peer_list_changed_callback != nullptr) {
|
|
g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata);
|
|
}
|
|
|
|
if (g->peer_on_leave != nullptr) {
|
|
g->peer_on_leave(g->object, groupnumber, peer_object);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Order peers with friends first and with more recently active earlier */
|
|
non_null()
|
|
static bool group_peer_less_handler(const void *object, const void *a, const void *b)
|
|
{
|
|
const Group_Peer *pa = (const Group_Peer *)a;
|
|
const Group_Peer *pb = (const Group_Peer *)b;
|
|
|
|
if (((pa->is_friend ? 1 : 0) ^ (pb->is_friend ? 1 : 0)) != 0) {
|
|
return pa->is_friend;
|
|
}
|
|
|
|
return cmp_uint(pb->last_active, pa->last_active) < 0;
|
|
}
|
|
|
|
non_null()
|
|
static const void *group_peer_get_handler(const void *arr, uint32_t index)
|
|
{
|
|
const Group_Peer *entries = (const Group_Peer *)arr;
|
|
return &entries[index];
|
|
}
|
|
|
|
non_null()
|
|
static void group_peer_set_handler(void *arr, uint32_t index, const void *val)
|
|
{
|
|
Group_Peer *entries = (Group_Peer *)arr;
|
|
const Group_Peer *entry = (const Group_Peer *)val;
|
|
entries[index] = *entry;
|
|
}
|
|
|
|
non_null()
|
|
static void *group_peer_subarr_handler(void *arr, uint32_t index, uint32_t size)
|
|
{
|
|
Group_Peer *entries = (Group_Peer *)arr;
|
|
return &entries[index];
|
|
}
|
|
|
|
non_null()
|
|
static void *group_peer_alloc_handler(const void *object, uint32_t size)
|
|
{
|
|
const Memory *mem = (const Memory *)object;
|
|
Group_Peer *tmp = (Group_Peer *)mem_valloc(mem, size, sizeof(Group_Peer));
|
|
|
|
if (tmp == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
|
|
non_null()
|
|
static void group_peer_delete_handler(const void *object, void *arr, uint32_t size)
|
|
{
|
|
const Memory *mem = (const Memory *)object;
|
|
mem_delete(mem, arr);
|
|
}
|
|
|
|
static const Sort_Funcs group_peer_cmp_funcs = {
|
|
group_peer_less_handler,
|
|
group_peer_get_handler,
|
|
group_peer_set_handler,
|
|
group_peer_subarr_handler,
|
|
group_peer_alloc_handler,
|
|
group_peer_delete_handler,
|
|
};
|
|
|
|
/** @brief Delete frozen peers as necessary to ensure at most `g->maxfrozen` remain.
|
|
*
|
|
* @retval true if any frozen peers are removed.
|
|
*/
|
|
non_null()
|
|
static bool delete_old_frozen(Group_c *g, const Memory *mem)
|
|
{
|
|
if (g->numfrozen <= g->maxfrozen) {
|
|
return false;
|
|
}
|
|
|
|
if (g->maxfrozen == 0) {
|
|
free(g->frozen);
|
|
g->frozen = nullptr;
|
|
g->numfrozen = 0;
|
|
return true;
|
|
}
|
|
|
|
merge_sort(g->frozen, g->numfrozen, mem, &group_peer_cmp_funcs);
|
|
|
|
Group_Peer *temp = (Group_Peer *)realloc(g->frozen, g->maxfrozen * sizeof(Group_Peer));
|
|
|
|
if (temp == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
g->frozen = temp;
|
|
|
|
g->numfrozen = g->maxfrozen;
|
|
|
|
return true;
|
|
}
|
|
|
|
non_null()
|
|
static bool try_send_rejoin(Group_Chats *g_c, Group_c *g, const uint8_t *real_pk);
|
|
|
|
non_null(1) nullable(4)
|
|
static bool freeze_peer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
Group_Peer *temp = (Group_Peer *)realloc(g->frozen, (g->numfrozen + 1) * sizeof(Group_Peer));
|
|
|
|
if (temp == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
g->frozen = temp;
|
|
g->frozen[g->numfrozen] = g->group[peer_index];
|
|
g->frozen[g->numfrozen].object = nullptr;
|
|
|
|
if (!delpeer(g_c, groupnumber, peer_index, userdata)) {
|
|
return false;
|
|
}
|
|
|
|
try_send_rejoin(g_c, g, g->frozen[g->numfrozen].real_pk);
|
|
|
|
++g->numfrozen;
|
|
|
|
delete_old_frozen(g, g_c->m->mem);
|
|
|
|
return true;
|
|
}
|
|
|
|
/** @brief 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.
|
|
*
|
|
* @retval true on success.
|
|
*/
|
|
non_null(1, 4) nullable(6)
|
|
static bool 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 false;
|
|
}
|
|
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
g->group[peer_index].nick_updated = true;
|
|
|
|
if (g_peer_nick_eq(&g->group[peer_index], nick, nick_len)) {
|
|
/* same name as already stored */
|
|
return true;
|
|
}
|
|
|
|
if (nick_len > 0) {
|
|
memcpy(g->group[peer_index].nick, nick, nick_len);
|
|
}
|
|
|
|
g->group[peer_index].nick_len = nick_len;
|
|
|
|
if (do_gc_callback && g_c->peer_name_callback != nullptr) {
|
|
g_c->peer_name_callback(g_c->m, groupnumber, peer_index, nick, nick_len, userdata);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** @brief Set the title for a group.
|
|
*
|
|
* @retval true on success.
|
|
*/
|
|
non_null(1, 4) nullable(6)
|
|
static bool settitle(Group_Chats *g_c, uint32_t groupnumber, int peer_index, const uint8_t *title, uint8_t title_len,
|
|
void *userdata)
|
|
{
|
|
if (title_len > MAX_NAME_LENGTH || title_len == 0) {
|
|
return false;
|
|
}
|
|
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (g_title_eq(g, title, title_len)) {
|
|
/* same title as already set */
|
|
return true;
|
|
}
|
|
|
|
memcpy(g->title, title, title_len);
|
|
g->title_len = title_len;
|
|
|
|
g->title_fresh = true;
|
|
|
|
if (g_c->title_callback != nullptr) {
|
|
g_c->title_callback(g_c->m, groupnumber, peer_index, title, title_len, userdata);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Check if the group has no online connection, and freeze all peers if so */
|
|
non_null(1) nullable(3)
|
|
static void check_disconnected(Group_Chats *g_c, uint32_t groupnumber, void *userdata)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < g->numpeers; ++i) {
|
|
while (i < g->numpeers && !pk_equal(g->group[i].real_pk, g->real_pk)) {
|
|
freeze_peer(g_c, groupnumber, i, userdata);
|
|
}
|
|
}
|
|
}
|
|
|
|
non_null(1) nullable(5)
|
|
static void set_conns_type_connections(Group_Chats *g_c, uint32_t groupnumber, int friendcon_id, uint8_t type,
|
|
void *userdata)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) {
|
|
continue;
|
|
}
|
|
|
|
if (g->connections[i].number != (unsigned int)friendcon_id) {
|
|
continue;
|
|
}
|
|
|
|
if (type == GROUPCHAT_CONNECTION_ONLINE) {
|
|
send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id);
|
|
} else {
|
|
g->connections[i].type = type;
|
|
check_disconnected(g_c, groupnumber, userdata);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Set the type for all connections with friendcon_id */
|
|
non_null(1) nullable(4)
|
|
static void set_conns_status_groups(Group_Chats *g_c, int friendcon_id, uint8_t type, void *userdata)
|
|
{
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
set_conns_type_connections(g_c, i, friendcon_id, type, userdata);
|
|
}
|
|
}
|
|
|
|
non_null()
|
|
static void rejoin_frozen_friend(Group_Chats *g_c, int friendcon_id)
|
|
{
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, friendcon_id);
|
|
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
Group_c *g = get_group_c(g_c, i);
|
|
|
|
if (g == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
for (uint32_t j = 0; j < g->numfrozen; ++j) {
|
|
if (pk_equal(g->frozen[j].real_pk, real_pk)) {
|
|
try_send_rejoin(g_c, g, real_pk);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
non_null(1) nullable(4)
|
|
static int g_handle_any_status(void *object, int friendcon_id, bool status, void *userdata)
|
|
{
|
|
Group_Chats *g_c = (Group_Chats *)object;
|
|
|
|
if (status) {
|
|
rejoin_frozen_friend(g_c, friendcon_id);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
non_null(1) nullable(4)
|
|
static int g_handle_status(void *object, int friendcon_id, bool status, void *userdata)
|
|
{
|
|
Group_Chats *g_c = (Group_Chats *)object;
|
|
|
|
if (status) { /* Went online */
|
|
set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CONNECTION_ONLINE, userdata);
|
|
} else { /* Went offline */
|
|
set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CONNECTION_CONNECTING, userdata);
|
|
// TODO(irungentoo): remove timedout connections?
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
non_null(1, 3) nullable(5)
|
|
static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata);
|
|
non_null(1, 3) nullable(5)
|
|
static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata);
|
|
|
|
/** @brief Add friend to group chat.
|
|
*
|
|
* @return connections index on success
|
|
* @retval -1 on failure.
|
|
*/
|
|
static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, Group_c *g, uint8_t reason,
|
|
bool lock)
|
|
{
|
|
uint16_t empty = MAX_GROUP_CONNECTIONS;
|
|
uint16_t ind = MAX_GROUP_CONNECTIONS;
|
|
|
|
for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) {
|
|
empty = i;
|
|
continue;
|
|
}
|
|
|
|
if (g->connections[i].number == (uint32_t)friendcon_id) {
|
|
ind = i; /* Already in list. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ind == MAX_GROUP_CONNECTIONS) {
|
|
if (empty == MAX_GROUP_CONNECTIONS) {
|
|
return -1;
|
|
}
|
|
|
|
if (lock) {
|
|
friend_connection_lock(g_c->fr_c, friendcon_id);
|
|
}
|
|
|
|
g->connections[empty].type = GROUPCHAT_CONNECTION_CONNECTING;
|
|
g->connections[empty].number = friendcon_id;
|
|
g->connections[empty].reasons = 0;
|
|
// TODO(irungentoo):
|
|
friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &g_handle_status, &g_handle_packet,
|
|
&handle_lossy, g_c, friendcon_id);
|
|
ind = empty;
|
|
}
|
|
|
|
if ((g->connections[ind].reasons & reason) == 0) {
|
|
g->connections[ind].reasons |= reason;
|
|
|
|
if (reason == GROUPCHAT_CONNECTION_REASON_INTRODUCER) {
|
|
++g->num_introducer_connections;
|
|
}
|
|
}
|
|
|
|
return ind;
|
|
}
|
|
|
|
non_null()
|
|
static bool send_peer_introduced(const Group_Chats *g_c, int friendcon_id, uint16_t group_num);
|
|
|
|
/** @brief Removes reason for keeping connection.
|
|
*
|
|
* Kills connection if this was the last reason.
|
|
*/
|
|
static void remove_connection_reason(Group_Chats *g_c, Group_c *g, uint16_t i, uint8_t reason)
|
|
{
|
|
if ((g->connections[i].reasons & reason) == 0) {
|
|
return;
|
|
}
|
|
|
|
g->connections[i].reasons &= ~reason;
|
|
|
|
if (reason == GROUPCHAT_CONNECTION_REASON_INTRODUCER) {
|
|
--g->num_introducer_connections;
|
|
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) {
|
|
send_peer_introduced(g_c, g->connections[i].number, g->connections[i].group_number);
|
|
}
|
|
}
|
|
|
|
if (g->connections[i].reasons == 0) {
|
|
kill_friend_connection(g_c->fr_c, g->connections[i].number);
|
|
g->connections[i].type = GROUPCHAT_CONNECTION_NONE;
|
|
}
|
|
}
|
|
|
|
/** @brief Creates a new groupchat and puts it in the chats array.
|
|
*
|
|
* @param rng Random number generator used for generating the group ID.
|
|
* @param type is one of `GROUPCHAT_TYPE_*`
|
|
*
|
|
* @return group number on success.
|
|
* @retval -1 on failure.
|
|
*/
|
|
int add_groupchat(Group_Chats *g_c, const Random *rng, uint8_t type)
|
|
{
|
|
const int32_t groupnumber = create_group_chat(g_c);
|
|
|
|
if (groupnumber == -1) {
|
|
return -1;
|
|
}
|
|
|
|
Group_c *g = &g_c->chats[groupnumber];
|
|
|
|
g->status = GROUPCHAT_STATUS_CONNECTED;
|
|
g->type = type;
|
|
new_symmetric_key(rng, g->id);
|
|
g->peer_number = 0; /* Founder is peer 0. */
|
|
memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE);
|
|
const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), 0, nullptr, true,
|
|
false);
|
|
|
|
if (peer_index == -1) {
|
|
return -1;
|
|
}
|
|
|
|
setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, nullptr, false);
|
|
|
|
return groupnumber;
|
|
}
|
|
|
|
non_null()
|
|
static bool group_leave(const Group_Chats *g_c, uint32_t groupnumber, bool permanent);
|
|
|
|
/** @brief Delete a groupchat from the chats array, informing the group first as
|
|
* appropriate.
|
|
*
|
|
* @retval true on success.
|
|
* @retval false if groupnumber is invalid.
|
|
*/
|
|
bool del_groupchat(Group_Chats *g_c, uint32_t groupnumber, bool leave_permanently)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
group_leave(g_c, groupnumber, leave_permanently);
|
|
|
|
for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) {
|
|
continue;
|
|
}
|
|
|
|
g->connections[i].type = GROUPCHAT_CONNECTION_NONE;
|
|
kill_friend_connection(g_c->fr_c, g->connections[i].number);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < g->numpeers; ++i) {
|
|
if (g->peer_on_leave != nullptr) {
|
|
g->peer_on_leave(g->object, groupnumber, g->group[i].object);
|
|
}
|
|
}
|
|
|
|
if (g->group_on_delete != nullptr) {
|
|
g->group_on_delete(g->object, groupnumber);
|
|
}
|
|
|
|
return wipe_group_chat(g_c, groupnumber);
|
|
}
|
|
|
|
non_null()
|
|
static const Group_Peer *peer_in_list(const Group_c *g, uint32_t peernumber, bool frozen)
|
|
{
|
|
const Group_Peer *list = frozen ? g->frozen : g->group;
|
|
const uint32_t num = frozen ? g->numfrozen : g->numpeers;
|
|
|
|
if (peernumber >= num) {
|
|
return nullptr;
|
|
}
|
|
|
|
return &list[peernumber];
|
|
}
|
|
|
|
/**
|
|
* @brief Copy the public key of (frozen, if frozen is true) peernumber who is in
|
|
* groupnumber to pk.
|
|
*
|
|
* @param pk must be CRYPTO_PUBLIC_KEY_SIZE long.
|
|
*
|
|
* @retval 0 on success
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if peernumber is invalid.
|
|
*/
|
|
int group_peer_pubkey(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *pk, bool frozen)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const Group_Peer *peer = peer_in_list(g, peernumber, frozen);
|
|
|
|
if (peer == nullptr) {
|
|
return -2;
|
|
}
|
|
|
|
memcpy(pk, peer->real_pk, CRYPTO_PUBLIC_KEY_SIZE);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Return the size of (frozen, if frozen is true) peernumber's name.
|
|
*
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if peernumber is invalid.
|
|
*/
|
|
int group_peername_size(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, bool frozen)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const Group_Peer *peer = peer_in_list(g, peernumber, frozen);
|
|
|
|
if (peer == nullptr) {
|
|
return -2;
|
|
}
|
|
|
|
return peer->nick_len;
|
|
}
|
|
|
|
/**
|
|
* @brief Copy the name of (frozen, if frozen is true) peernumber who is in
|
|
* groupnumber to name.
|
|
*
|
|
* @param name must be at least MAX_NAME_LENGTH long.
|
|
*
|
|
* @return length of name if success
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if peernumber is invalid.
|
|
*/
|
|
int group_peername(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *name, bool frozen)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const Group_Peer *peer = peer_in_list(g, peernumber, frozen);
|
|
|
|
if (peer == nullptr) {
|
|
return -2;
|
|
}
|
|
|
|
if (peer->nick_len > 0) {
|
|
memcpy(name, peer->nick, peer->nick_len);
|
|
}
|
|
|
|
return peer->nick_len;
|
|
}
|
|
|
|
/**
|
|
* @brief Copy last active timestamp of frozen peernumber who is in groupnumber to
|
|
* last_active.
|
|
*
|
|
* @retval 0 on success.
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if peernumber is invalid.
|
|
*/
|
|
int group_frozen_last_active(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber,
|
|
uint64_t *last_active)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (peernumber >= g->numfrozen) {
|
|
return -2;
|
|
}
|
|
|
|
*last_active = g->frozen[peernumber].last_active;
|
|
return 0;
|
|
}
|
|
|
|
/** @brief Set maximum number of frozen peers.
|
|
*
|
|
* @retval 0 on success.
|
|
* @retval -1 if groupnumber is invalid.
|
|
*/
|
|
int group_set_max_frozen(const Group_Chats *g_c, uint32_t groupnumber, uint32_t maxfrozen)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
g->maxfrozen = maxfrozen;
|
|
delete_old_frozen(g, g_c->m->mem);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @return the number of (frozen, if frozen is true) peers in the group chat on success.
|
|
* @retval -1 if groupnumber is invalid.
|
|
*/
|
|
int group_number_peers(const Group_Chats *g_c, uint32_t groupnumber, bool frozen)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
return frozen ? g->numfrozen : g->numpeers;
|
|
}
|
|
|
|
/**
|
|
* @retval 1 if the peernumber corresponds to ours.
|
|
* @retval 0 if the peernumber is not ours.
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if peernumber is invalid.
|
|
* @retval -3 if we are not connected to the group chat.
|
|
*/
|
|
int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (peernumber >= g->numpeers) {
|
|
return -2;
|
|
}
|
|
|
|
if (g->status != GROUPCHAT_STATUS_CONNECTED) {
|
|
return -3;
|
|
}
|
|
|
|
return (g->peer_number == g->group[peernumber].peer_number) ? 1 : 0;
|
|
}
|
|
|
|
/** @brief return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is.
|
|
*
|
|
* @retval -1 on failure.
|
|
* @return type on success.
|
|
*/
|
|
int group_get_type(const Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
return g->type;
|
|
}
|
|
|
|
/** @brief Copies the unique id of `group_chat[groupnumber]` into `id`.
|
|
*
|
|
* @retval false on failure.
|
|
* @retval true on success.
|
|
*/
|
|
bool conference_get_id(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *id)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (id != nullptr) {
|
|
memcpy(id, g->id, sizeof(g->id));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** @brief Send a group packet to friendcon_id.
|
|
*
|
|
* @retval true on success
|
|
* @retval false on failure
|
|
*/
|
|
non_null()
|
|
static bool send_packet_group_peer(const Friend_Connections *fr_c, int 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 false;
|
|
}
|
|
|
|
group_num = net_htons(group_num);
|
|
const uint32_t packet_size = 1 + sizeof(uint16_t) + length;
|
|
VLA(uint8_t, packet, packet_size);
|
|
packet[0] = packet_id;
|
|
memcpy(packet + 1, &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, packet_size, false) != -1;
|
|
}
|
|
|
|
/** @brief Send a group lossy packet to friendcon_id.
|
|
*
|
|
* @retval true on success
|
|
* @retval false on failure
|
|
*/
|
|
non_null()
|
|
static bool send_lossy_group_peer(const Friend_Connections *fr_c, int 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 false;
|
|
}
|
|
|
|
group_num = net_htons(group_num);
|
|
const uint32_t packet_size = 1 + sizeof(uint16_t) + length;
|
|
VLA(uint8_t, packet, packet_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, packet_size) != -1;
|
|
}
|
|
|
|
/** @brief invite friendnumber to groupnumber.
|
|
*
|
|
* @retval 0 on success.
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if invite packet failed to send.
|
|
* @retval -3 if we are not connected to the group chat.
|
|
*/
|
|
int invite_friend(const Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (g->status != GROUPCHAT_STATUS_CONNECTED) {
|
|
return -3;
|
|
}
|
|
|
|
uint8_t invite[INVITE_PACKET_SIZE];
|
|
invite[0] = INVITE_ID;
|
|
const uint16_t groupchat_num = net_htons((uint16_t)groupnumber);
|
|
memcpy(invite + 1, &groupchat_num, sizeof(groupchat_num));
|
|
invite[1 + sizeof(groupchat_num)] = g->type;
|
|
memcpy(invite + 1 + sizeof(groupchat_num) + 1, g->id, GROUP_ID_LENGTH);
|
|
|
|
if (send_conference_invite_packet(g_c->m, friendnumber, invite, sizeof(invite))) {
|
|
return 0;
|
|
}
|
|
|
|
return -2;
|
|
}
|
|
|
|
/** @brief Send a rejoin packet to a peer if we have a friend connection to the peer.
|
|
* @retval true if a packet was sent.
|
|
* @retval false otherwise.
|
|
*/
|
|
static bool try_send_rejoin(Group_Chats *g_c, Group_c *g, const uint8_t *real_pk)
|
|
{
|
|
const int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, real_pk);
|
|
|
|
if (friendcon_id == -1) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t packet[1 + 1 + GROUP_ID_LENGTH];
|
|
packet[0] = PACKET_ID_REJOIN_CONFERENCE;
|
|
packet[1] = g->type;
|
|
memcpy(packet + 2, g->id, GROUP_ID_LENGTH);
|
|
|
|
if (write_cryptpacket(friendconn_net_crypto(g_c->fr_c), friend_connection_crypt_connection_id(g_c->fr_c, friendcon_id),
|
|
packet, sizeof(packet), false) == -1) {
|
|
return false;
|
|
}
|
|
|
|
add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, true);
|
|
|
|
return true;
|
|
}
|
|
|
|
non_null()
|
|
static bool send_peer_query(const Group_Chats *g_c, int friendcon_id, uint16_t group_num);
|
|
|
|
non_null()
|
|
static bool send_invite_response(Group_Chats *g_c, int groupnumber, uint32_t friendnumber, const uint8_t *data,
|
|
uint16_t length);
|
|
|
|
/** @brief Join a group (we need to have been invited first).
|
|
*
|
|
* @param expected_type is the groupchat type we expect the chat we are joining
|
|
* to have.
|
|
*
|
|
* @return group number on success.
|
|
* @retval -1 if data length is invalid.
|
|
* @retval -2 if group is not the expected type.
|
|
* @retval -3 if friendnumber is invalid.
|
|
* @retval -4 if client is already in this group.
|
|
* @retval -5 if group instance failed to initialize.
|
|
* @retval -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)
|
|
{
|
|
if (length != sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) {
|
|
return -1;
|
|
}
|
|
|
|
if (data[sizeof(uint16_t)] != expected_type) {
|
|
return -2;
|
|
}
|
|
|
|
const int friendcon_id = getfriendcon_id(g_c->m, friendnumber);
|
|
|
|
if (friendcon_id == -1) {
|
|
return -3;
|
|
}
|
|
|
|
if (get_group_num(g_c, data[sizeof(uint16_t)], data + sizeof(uint16_t) + 1) != -1) {
|
|
return -4;
|
|
}
|
|
|
|
const int groupnumber = create_group_chat(g_c);
|
|
|
|
if (groupnumber == -1) {
|
|
return -5;
|
|
}
|
|
|
|
Group_c *g = &g_c->chats[groupnumber];
|
|
|
|
g->status = GROUPCHAT_STATUS_VALID;
|
|
memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE);
|
|
|
|
if (!send_invite_response(g_c, groupnumber, friendnumber, data, length)) {
|
|
g->status = GROUPCHAT_STATUS_NONE;
|
|
return -6;
|
|
}
|
|
|
|
return groupnumber;
|
|
}
|
|
|
|
static bool send_invite_response(Group_Chats *g_c, int groupnumber, uint32_t friendnumber, const uint8_t *data,
|
|
uint16_t length)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
const bool member = g->status == GROUPCHAT_STATUS_CONNECTED;
|
|
|
|
const uint32_t response_size = member ? INVITE_MEMBER_PACKET_SIZE : INVITE_ACCEPT_PACKET_SIZE;
|
|
VLA(uint8_t, response, response_size);
|
|
response[0] = member ? INVITE_MEMBER_ID : INVITE_ACCEPT_ID;
|
|
net_pack_u16(response + 1, groupnumber);
|
|
memcpy(response + 1 + sizeof(uint16_t), data, length);
|
|
|
|
if (member) {
|
|
net_pack_u16(response + 1 + sizeof(uint16_t) + length, g->peer_number);
|
|
}
|
|
|
|
if (!send_conference_invite_packet(g_c->m, friendnumber, response, response_size)) {
|
|
return false;
|
|
}
|
|
|
|
if (!member) {
|
|
g->type = data[sizeof(uint16_t)];
|
|
memcpy(g->id, data + sizeof(uint16_t) + 1, GROUP_ID_LENGTH);
|
|
}
|
|
|
|
uint16_t other_groupnum;
|
|
net_unpack_u16(data, &other_groupnum);
|
|
|
|
const int friendcon_id = getfriendcon_id(g_c->m, friendnumber);
|
|
|
|
if (friendcon_id == -1) {
|
|
return false;
|
|
}
|
|
|
|
const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, true);
|
|
|
|
if (member) {
|
|
add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCING, false);
|
|
}
|
|
|
|
if (connection_index != -1) {
|
|
g->connections[connection_index].group_number = other_groupnum;
|
|
g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE;
|
|
}
|
|
|
|
send_peer_query(g_c, friendcon_id, other_groupnum);
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Set handlers for custom lossy packets. */
|
|
void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, lossy_packet_cb *function)
|
|
{
|
|
g_c->lossy_packethandlers[byte] = function;
|
|
}
|
|
|
|
/** Set the callback for group invites. */
|
|
void g_callback_group_invite(Group_Chats *g_c, g_conference_invite_cb *function)
|
|
{
|
|
g_c->invite_callback = function;
|
|
}
|
|
|
|
/** Set the callback for group connection. */
|
|
void g_callback_group_connected(Group_Chats *g_c, g_conference_connected_cb *function)
|
|
{
|
|
g_c->connected_callback = function;
|
|
}
|
|
|
|
/** Set the callback for group messages. */
|
|
void g_callback_group_message(Group_Chats *g_c, g_conference_message_cb *function)
|
|
{
|
|
g_c->message_callback = function;
|
|
}
|
|
|
|
/** @brief Set callback function for peer nickname changes.
|
|
*
|
|
* It gets called every time a peer changes their nickname.
|
|
*/
|
|
void g_callback_peer_name(Group_Chats *g_c, peer_name_cb *function)
|
|
{
|
|
g_c->peer_name_callback = function;
|
|
}
|
|
|
|
/** @brief Set callback function for peer list changes.
|
|
*
|
|
* It gets called every time the name list changes(new peer, deleted peer)
|
|
*/
|
|
void g_callback_peer_list_changed(Group_Chats *g_c, peer_list_changed_cb *function)
|
|
{
|
|
g_c->peer_list_changed_callback = function;
|
|
}
|
|
|
|
/** Set callback function for title changes. */
|
|
void g_callback_group_title(Group_Chats *g_c, title_cb *function)
|
|
{
|
|
g_c->title_callback = function;
|
|
}
|
|
|
|
/** @brief Set a function to be called when a new peer joins a group chat.
|
|
*
|
|
* @retval 0 on success.
|
|
* @retval -1 on failure.
|
|
*/
|
|
int callback_groupchat_peer_new(const Group_Chats *g_c, uint32_t groupnumber, peer_on_join_cb *function)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
g->peer_on_join = function;
|
|
return 0;
|
|
}
|
|
|
|
/** @brief Set a function to be called when a peer leaves a group chat.
|
|
*
|
|
* @retval 0 on success.
|
|
* @retval -1 on failure.
|
|
*/
|
|
int callback_groupchat_peer_delete(const Group_Chats *g_c, uint32_t groupnumber, peer_on_leave_cb *function)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
g->peer_on_leave = function;
|
|
return 0;
|
|
}
|
|
|
|
/** @brief Set a function to be called when the group chat is deleted.
|
|
*
|
|
* @retval 0 on success.
|
|
* @retval -1 on failure.
|
|
*/
|
|
int callback_groupchat_delete(const Group_Chats *g_c, uint32_t groupnumber, group_on_delete_cb *function)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
g->group_on_delete = function;
|
|
return 0;
|
|
}
|
|
|
|
non_null(1) nullable(4)
|
|
static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint8_t message_id, const uint8_t *data,
|
|
uint16_t len);
|
|
|
|
/** @brief send a ping message
|
|
* return true on success
|
|
*/
|
|
non_null()
|
|
static bool group_ping_send(const Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
return send_message_group(g_c, groupnumber, GROUP_MESSAGE_PING_ID, nullptr, 0) > 0;
|
|
}
|
|
|
|
/** @brief send a new_peer message
|
|
* return true on success
|
|
*/
|
|
non_null()
|
|
static bool group_new_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num, 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));
|
|
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);
|
|
|
|
return send_message_group(g_c, groupnumber, GROUP_MESSAGE_NEW_PEER_ID, packet, sizeof(packet)) > 0;
|
|
}
|
|
|
|
/** @brief send a kill_peer message
|
|
* return true on success
|
|
*/
|
|
non_null()
|
|
static bool group_kill_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num)
|
|
{
|
|
uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH];
|
|
|
|
peer_num = net_htons(peer_num);
|
|
memcpy(packet, &peer_num, sizeof(uint16_t));
|
|
|
|
return send_message_group(g_c, groupnumber, GROUP_MESSAGE_KILL_PEER_ID, packet, sizeof(packet)) > 0;
|
|
}
|
|
|
|
/** @brief send a freeze_peer message
|
|
* return true on success
|
|
*/
|
|
non_null()
|
|
static bool group_freeze_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num)
|
|
{
|
|
uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH];
|
|
|
|
peer_num = net_htons(peer_num);
|
|
memcpy(packet, &peer_num, sizeof(uint16_t));
|
|
|
|
return send_message_group(g_c, groupnumber, GROUP_MESSAGE_FREEZE_PEER_ID, packet, sizeof(packet)) > 0;
|
|
}
|
|
|
|
/** @brief send a name message
|
|
* return true on success
|
|
*/
|
|
non_null()
|
|
static bool group_name_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *nick, uint16_t nick_len)
|
|
{
|
|
if (nick_len > MAX_NAME_LENGTH) {
|
|
return false;
|
|
}
|
|
|
|
return send_message_group(g_c, groupnumber, GROUP_MESSAGE_NAME_ID, nick, nick_len) > 0;
|
|
}
|
|
|
|
/** @brief send message to announce leaving group
|
|
* return true on success
|
|
*/
|
|
static bool group_leave(const Group_Chats *g_c, uint32_t groupnumber, bool permanent)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (permanent) {
|
|
return group_kill_peer_send(g_c, groupnumber, g->peer_number);
|
|
} else {
|
|
return group_freeze_peer_send(g_c, groupnumber, g->peer_number);
|
|
}
|
|
}
|
|
|
|
/** @brief set the group's title, limited to MAX_NAME_LENGTH.
|
|
* @retval 0 on success
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if title is too long or empty.
|
|
* @retval -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)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (title_len > MAX_NAME_LENGTH || title_len == 0) {
|
|
return -2;
|
|
}
|
|
|
|
/* same as already set? */
|
|
if (g_title_eq(g, title, title_len)) {
|
|
return 0;
|
|
}
|
|
|
|
memcpy(g->title, title, title_len);
|
|
g->title_len = title_len;
|
|
|
|
if (g->numpeers == 1) {
|
|
return 0;
|
|
}
|
|
|
|
if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_TITLE_ID, title, title_len) > 0) {
|
|
return 0;
|
|
}
|
|
|
|
return -3;
|
|
}
|
|
|
|
/** @brief return the group's title size.
|
|
* @retval -1 of groupnumber is invalid.
|
|
* @retval -2 if title is too long or empty.
|
|
*/
|
|
int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (g->title_len > MAX_NAME_LENGTH || g->title_len == 0) {
|
|
return -2;
|
|
}
|
|
|
|
return g->title_len;
|
|
}
|
|
|
|
/** @brief 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.
|
|
*
|
|
* @return length of copied title if success.
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if title is too long or empty.
|
|
*/
|
|
int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (g->title_len > MAX_NAME_LENGTH || g->title_len == 0) {
|
|
return -2;
|
|
}
|
|
|
|
memcpy(title, g->title, g->title_len);
|
|
return g->title_len;
|
|
}
|
|
|
|
non_null()
|
|
static bool get_peer_number(const Group_c *g, const uint8_t *real_pk, uint16_t *peer_number)
|
|
{
|
|
const int peer_index = peer_in_group(g, real_pk);
|
|
|
|
if (peer_index >= 0) {
|
|
*peer_number = g->group[peer_index].peer_number;
|
|
return true;
|
|
}
|
|
|
|
const int frozen_index = frozen_in_group(g, real_pk);
|
|
|
|
if (frozen_index >= 0) {
|
|
*peer_number = g->frozen[frozen_index].peer_number;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
non_null(1, 3) nullable(5)
|
|
static void handle_friend_invite_packet(Messenger *m, uint32_t friend_number, const uint8_t *cookie, uint16_t length,
|
|
void *user_data)
|
|
{
|
|
Group_Chats *g_c = m->conferences_object;
|
|
|
|
if (length <= 1) {
|
|
return;
|
|
}
|
|
|
|
switch (cookie[0]) {
|
|
case INVITE_ID: {
|
|
if (length != INVITE_PACKET_SIZE) {
|
|
return;
|
|
}
|
|
|
|
const int groupnumber = get_group_num(g_c, cookie[1 + sizeof(uint16_t)], cookie + 1 + sizeof(uint16_t) + 1);
|
|
|
|
const uint8_t *invite_data = cookie + 1;
|
|
const uint16_t invite_length = length - 1;
|
|
|
|
if (groupnumber == -1) {
|
|
if (g_c->invite_callback != nullptr) {
|
|
g_c->invite_callback(m, friend_number, invite_data[sizeof(uint16_t)], invite_data, invite_length, user_data);
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g != nullptr && g->status == GROUPCHAT_STATUS_CONNECTED) {
|
|
send_invite_response(g_c, groupnumber, friend_number, invite_data, invite_length);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case INVITE_ACCEPT_ID:
|
|
case INVITE_MEMBER_ID: {
|
|
const bool member = cookie[0] == INVITE_MEMBER_ID;
|
|
|
|
if (length != (member ? INVITE_MEMBER_PACKET_SIZE : INVITE_ACCEPT_PACKET_SIZE)) {
|
|
return;
|
|
}
|
|
|
|
uint16_t other_groupnum;
|
|
uint16_t groupnum;
|
|
net_unpack_u16(cookie + 1, &other_groupnum);
|
|
net_unpack_u16(cookie + 1 + sizeof(uint16_t), &groupnum);
|
|
|
|
Group_c *g = get_group_c(g_c, groupnum);
|
|
|
|
if (g == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (cookie[1 + sizeof(uint16_t) * 2] != g->type) {
|
|
return;
|
|
}
|
|
|
|
if (!group_id_eq(cookie + 1 + sizeof(uint16_t) * 2 + 1, g->id)) {
|
|
return;
|
|
}
|
|
|
|
uint16_t peer_number;
|
|
|
|
if (member) {
|
|
net_unpack_u16(cookie + 1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH, &peer_number);
|
|
} else {
|
|
/* TODO(irungentoo): what if two people enter the group at the
|
|
* same time and are given the same peer_number by different
|
|
* nodes? */
|
|
peer_number = random_u16(m->rng);
|
|
|
|
unsigned int tries = 0;
|
|
|
|
while (get_peer_index(g, peer_number) != -1 || get_frozen_index(g, peer_number) != -1) {
|
|
peer_number = random_u16(m->rng);
|
|
++tries;
|
|
|
|
if (tries > 32) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
const int friendcon_id = getfriendcon_id(m, friend_number);
|
|
|
|
if (friendcon_id == -1) {
|
|
// TODO(iphydf): Log something?
|
|
return;
|
|
}
|
|
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
uint8_t 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, user_data, true, true);
|
|
const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g,
|
|
GROUPCHAT_CONNECTION_REASON_INTRODUCING, true);
|
|
|
|
if (member) {
|
|
add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, false);
|
|
send_peer_query(g_c, friendcon_id, other_groupnum);
|
|
}
|
|
|
|
if (connection_index != -1) {
|
|
g->connections[connection_index].group_number = other_groupnum;
|
|
g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE;
|
|
}
|
|
|
|
group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk);
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @brief Find index of friend in the connections list.
|
|
*
|
|
* return index on success
|
|
* return -1 on failure.
|
|
*/
|
|
non_null()
|
|
static int friend_in_connections(const Group_c *g, int friendcon_id)
|
|
{
|
|
for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) {
|
|
continue;
|
|
}
|
|
|
|
if (g->connections[i].number == (uint32_t)friendcon_id) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/** return number of connections. */
|
|
non_null()
|
|
static unsigned int count_connected(const Group_c *g)
|
|
{
|
|
unsigned int count = 0;
|
|
|
|
for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) {
|
|
++count;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static bool send_packet_online(const Friend_Connections *fr_c, int friendcon_id, uint16_t group_num,
|
|
uint8_t type, const uint8_t *id)
|
|
{
|
|
uint8_t packet[1 + ONLINE_PACKET_DATA_SIZE];
|
|
group_num = net_htons(group_num);
|
|
packet[0] = PACKET_ID_ONLINE_PACKET;
|
|
memcpy(packet + 1, &group_num, sizeof(uint16_t));
|
|
packet[1 + sizeof(uint16_t)] = type;
|
|
memcpy(packet + 1 + sizeof(uint16_t) + 1, id, GROUP_ID_LENGTH);
|
|
return write_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), packet,
|
|
sizeof(packet), false) != -1;
|
|
}
|
|
|
|
non_null()
|
|
static bool ping_groupchat(const Group_Chats *g_c, uint32_t groupnumber);
|
|
|
|
non_null()
|
|
static int handle_packet_online(const Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length)
|
|
{
|
|
if (length != ONLINE_PACKET_DATA_SIZE) {
|
|
return -1;
|
|
}
|
|
|
|
const int groupnumber = get_group_num(g_c, data[sizeof(uint16_t)], data + sizeof(uint16_t) + 1);
|
|
|
|
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);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const int index = friend_in_connections(g, friendcon_id);
|
|
|
|
if (index == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (g->connections[index].type == GROUPCHAT_CONNECTION_ONLINE) {
|
|
return -1;
|
|
}
|
|
|
|
if (count_connected(g) == 0 || (g->connections[index].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER) != 0) {
|
|
send_peer_query(g_c, friendcon_id, other_groupnum);
|
|
}
|
|
|
|
g->connections[index].group_number = other_groupnum;
|
|
g->connections[index].type = GROUPCHAT_CONNECTION_ONLINE;
|
|
send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id);
|
|
|
|
if ((g->connections[index].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCING) != 0) {
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id);
|
|
|
|
const int peer_index = peer_in_group(g, real_pk);
|
|
|
|
if (peer_index != -1) {
|
|
group_new_peer_send(g_c, groupnumber, g->group[peer_index].peer_number, real_pk, temp_pk);
|
|
}
|
|
|
|
g->need_send_name = true;
|
|
}
|
|
|
|
ping_groupchat(g_c, groupnumber);
|
|
|
|
return 0;
|
|
}
|
|
|
|
non_null(1, 3) nullable(5)
|
|
static int handle_packet_rejoin(Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length,
|
|
void *userdata)
|
|
{
|
|
if (length < 1 + GROUP_ID_LENGTH) {
|
|
return -1;
|
|
}
|
|
|
|
const int32_t groupnum = get_group_num(g_c, *data, data + 1);
|
|
|
|
Group_c *g = get_group_c(g_c, groupnum);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id);
|
|
|
|
uint16_t peer_number;
|
|
|
|
if (!get_peer_number(g, real_pk, &peer_number)) {
|
|
return -1;
|
|
}
|
|
|
|
addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true, true);
|
|
const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g,
|
|
GROUPCHAT_CONNECTION_REASON_INTRODUCING, true);
|
|
|
|
if (connection_index != -1) {
|
|
send_packet_online(g_c->fr_c, friendcon_id, groupnum, g->type, g->id);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// we could send title with invite, but then if it changes between sending and accepting inv, joinee won't see it
|
|
|
|
/**
|
|
* @retval true on success.
|
|
* @retval false on failure
|
|
*/
|
|
static bool send_peer_introduced(const Group_Chats *g_c, int friendcon_id, uint16_t group_num)
|
|
{
|
|
uint8_t packet[1];
|
|
packet[0] = PEER_INTRODUCED_ID;
|
|
return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet));
|
|
}
|
|
|
|
/**
|
|
* @retval true on success.
|
|
* @retval false on failure
|
|
*/
|
|
static bool send_peer_query(const Group_Chats *g_c, int friendcon_id, uint16_t 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));
|
|
}
|
|
|
|
/**
|
|
* @return number of peers sent on success.
|
|
* @retval 0 on failure.
|
|
*/
|
|
non_null()
|
|
static unsigned int send_peers(const Group_Chats *g_c, const Group_c *g, int friendcon_id, uint16_t group_num)
|
|
{
|
|
uint8_t response_packet[MAX_CRYPTO_DATA_SIZE - (1 + sizeof(uint16_t))];
|
|
response_packet[0] = PEER_RESPONSE_ID;
|
|
uint8_t *p = response_packet + 1;
|
|
|
|
uint16_t sent = 0;
|
|
|
|
for (uint32_t i = 0; i <= g->numpeers; ++i) {
|
|
if (i == g->numpeers
|
|
|| (p - response_packet) + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1 + g->group[i].nick_len >
|
|
sizeof(response_packet)) {
|
|
if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, response_packet,
|
|
p - response_packet)) {
|
|
sent = i;
|
|
} else {
|
|
return sent;
|
|
}
|
|
|
|
if (i == g->numpeers) {
|
|
break;
|
|
}
|
|
|
|
p = response_packet + 1;
|
|
}
|
|
|
|
const uint16_t peer_num = net_htons(g->group[i].peer_number);
|
|
memcpy(p, &peer_num, sizeof(peer_num));
|
|
p += sizeof(peer_num);
|
|
memcpy(p, g->group[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE);
|
|
p += CRYPTO_PUBLIC_KEY_SIZE;
|
|
memcpy(p, g->group[i].temp_pk, CRYPTO_PUBLIC_KEY_SIZE);
|
|
p += CRYPTO_PUBLIC_KEY_SIZE;
|
|
*p = g->group[i].nick_len;
|
|
p += 1;
|
|
memcpy(p, g->group[i].nick, g->group[i].nick_len);
|
|
p += g->group[i].nick_len;
|
|
}
|
|
|
|
if (g->title_len > 0) {
|
|
const uint32_t title_packet_size = 1 + g->title_len;
|
|
VLA(uint8_t, title_packet, title_packet_size);
|
|
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, group_num,
|
|
title_packet, title_packet_size);
|
|
}
|
|
|
|
return sent;
|
|
}
|
|
|
|
non_null(1, 3) nullable(5)
|
|
static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length,
|
|
void *userdata)
|
|
{
|
|
if (length == 0) {
|
|
return -1;
|
|
}
|
|
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
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);
|
|
d += sizeof(uint16_t);
|
|
|
|
if (g->status == GROUPCHAT_STATUS_VALID
|
|
&& pk_equal(d, nc_get_self_public_key(g_c->m->net_crypto))) {
|
|
g->peer_number = peer_num;
|
|
g->status = GROUPCHAT_STATUS_CONNECTED;
|
|
|
|
if (g_c->connected_callback != nullptr) {
|
|
g_c->connected_callback(g_c->m, groupnumber, userdata);
|
|
}
|
|
|
|
g->need_send_name = true;
|
|
}
|
|
|
|
const int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, false, true);
|
|
|
|
if (peer_index == -1) {
|
|
return -1;
|
|
}
|
|
|
|
d += CRYPTO_PUBLIC_KEY_SIZE * 2;
|
|
const uint8_t name_length = *d;
|
|
d += 1;
|
|
|
|
if (name_length > (length - (d - data)) || name_length > MAX_NAME_LENGTH) {
|
|
return -1;
|
|
}
|
|
|
|
if (!g->group[peer_index].nick_updated) {
|
|
setnick(g_c, groupnumber, peer_index, d, name_length, userdata, true);
|
|
}
|
|
|
|
d += name_length;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
non_null(1, 3) nullable(6)
|
|
static void handle_direct_packet(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length,
|
|
uint32_t connection_index, void *userdata)
|
|
{
|
|
if (length == 0) {
|
|
return;
|
|
}
|
|
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return;
|
|
}
|
|
|
|
switch (data[0]) {
|
|
case PEER_INTRODUCED_ID: {
|
|
remove_connection_reason(g_c, g, connection_index, GROUPCHAT_CONNECTION_REASON_INTRODUCING);
|
|
break;
|
|
}
|
|
|
|
case PEER_QUERY_ID: {
|
|
if (g->connections[connection_index].type != GROUPCHAT_CONNECTION_ONLINE) {
|
|
return;
|
|
}
|
|
|
|
send_peers(g_c, g, g->connections[connection_index].number, g->connections[connection_index].group_number);
|
|
break;
|
|
}
|
|
|
|
case PEER_RESPONSE_ID: {
|
|
handle_send_peers(g_c, groupnumber, data + 1, length - 1, userdata);
|
|
break;
|
|
}
|
|
|
|
case PEER_TITLE_ID: {
|
|
if (!g->title_fresh) {
|
|
settitle(g_c, groupnumber, -1, data + 1, length - 1, userdata);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @brief Send message to all connections except receiver (if receiver isn't -1)
|
|
*
|
|
* NOTE: this function appends the group chat number to the data passed to it.
|
|
*
|
|
* @return number of messages sent.
|
|
*/
|
|
non_null()
|
|
static unsigned int send_message_all_connections(const Group_Chats *g_c, const Group_c *g, const uint8_t *data,
|
|
uint16_t length, int receiver)
|
|
{
|
|
uint16_t sent = 0;
|
|
|
|
for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE) {
|
|
continue;
|
|
}
|
|
|
|
if ((int)i == receiver) {
|
|
continue;
|
|
}
|
|
|
|
if (send_packet_group_peer(g_c->fr_c, g->connections[i].number, PACKET_ID_MESSAGE_CONFERENCE,
|
|
g->connections[i].group_number, data, length)) {
|
|
++sent;
|
|
}
|
|
}
|
|
|
|
return sent;
|
|
}
|
|
|
|
/** @brief Send lossy message to all connections except receiver (if receiver isn't -1)
|
|
*
|
|
* NOTE: this function appends the group chat number to the data passed to it.
|
|
*
|
|
* @return number of messages sent.
|
|
*/
|
|
non_null()
|
|
static unsigned int send_lossy_all_connections(const Group_Chats *g_c, const Group_c *g, const uint8_t *data,
|
|
uint16_t length, int receiver)
|
|
{
|
|
unsigned int sent = 0;
|
|
unsigned int num_connected_closest = 0;
|
|
unsigned int connected_closest[DESIRED_CLOSEST] = {0};
|
|
|
|
for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE) {
|
|
continue;
|
|
}
|
|
|
|
if ((int)i == receiver) {
|
|
continue;
|
|
}
|
|
|
|
if ((g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) != 0) {
|
|
connected_closest[num_connected_closest] = i;
|
|
++num_connected_closest;
|
|
continue;
|
|
}
|
|
|
|
if (send_lossy_group_peer(g_c->fr_c, g->connections[i].number, PACKET_ID_LOSSY_CONFERENCE,
|
|
g->connections[i].group_number, data, length)) {
|
|
++sent;
|
|
}
|
|
}
|
|
|
|
if (num_connected_closest == 0) {
|
|
return sent;
|
|
}
|
|
|
|
unsigned int to_send[2] = {0, 0};
|
|
uint64_t comp_val_old[2] = {(uint64_t) -1, (uint64_t) -1};
|
|
|
|
for (unsigned int i = 0; i < num_connected_closest; ++i) {
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE] = {0};
|
|
get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[connected_closest[i]].number);
|
|
const uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk);
|
|
|
|
for (uint8_t j = 0; j < 2; ++j) {
|
|
if (j > 0 ? (comp_val > comp_val_old[j]) : (comp_val < comp_val_old[j])) {
|
|
to_send[j] = connected_closest[i];
|
|
comp_val_old[j] = comp_val;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uint8_t j = 0; j < 2; ++j) {
|
|
if (j > 0 && to_send[1] == to_send[0]) {
|
|
break;
|
|
}
|
|
|
|
if (send_lossy_group_peer(g_c->fr_c, g->connections[to_send[j]].number, PACKET_ID_LOSSY_CONFERENCE,
|
|
g->connections[to_send[j]].group_number, data, length)) {
|
|
++sent;
|
|
}
|
|
}
|
|
|
|
return sent;
|
|
}
|
|
|
|
/** @brief Send data of len with message_id to groupnumber.
|
|
*
|
|
* @return number of peers it was sent to on success.
|
|
* @retval -1 if groupnumber is invalid.
|
|
* @retval -2 if message is too long.
|
|
* @retval -3 if we are not connected to the group.
|
|
* @retval -4 if message failed to send.
|
|
*/
|
|
static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint8_t message_id, const uint8_t *data,
|
|
uint16_t len)
|
|
{
|
|
assert(len == 0 || data != nullptr);
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (len > MAX_GROUP_MESSAGE_DATA_LEN) {
|
|
return -2;
|
|
}
|
|
|
|
if (g->status != GROUPCHAT_STATUS_CONNECTED || count_connected(g) == 0) {
|
|
return -3;
|
|
}
|
|
|
|
const uint16_t packet_size = sizeof(uint16_t) + sizeof(uint32_t) + 1 + len;
|
|
VLA(uint8_t, packet, packet_size);
|
|
const uint16_t peer_num = net_htons(g->peer_number);
|
|
memcpy(packet, &peer_num, sizeof(peer_num));
|
|
|
|
++g->message_number;
|
|
|
|
if (g->message_number == 0) {
|
|
++g->message_number;
|
|
}
|
|
|
|
const uint32_t message_num = net_htonl(g->message_number);
|
|
memcpy(packet + sizeof(uint16_t), &message_num, sizeof(message_num));
|
|
|
|
packet[sizeof(uint16_t) + sizeof(uint32_t)] = message_id;
|
|
|
|
if (len != 0) {
|
|
memcpy(packet + sizeof(uint16_t) + sizeof(uint32_t) + 1, data, len);
|
|
}
|
|
|
|
const unsigned int ret = send_message_all_connections(g_c, g, packet, packet_size, -1);
|
|
|
|
if (ret == 0) {
|
|
return -4;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** @brief send a group message
|
|
* @retval 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)
|
|
{
|
|
const int ret = send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length);
|
|
|
|
if (ret > 0) {
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** @brief send a group action
|
|
* @retval 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)
|
|
{
|
|
const int ret = send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length);
|
|
|
|
if (ret > 0) {
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** @brief High level function to send custom lossy packets.
|
|
*
|
|
* @retval -1 on failure.
|
|
* @retval 0 on success.
|
|
*/
|
|
int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length)
|
|
{
|
|
// TODO(irungentoo): length check here?
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const uint16_t packet_size = sizeof(uint16_t) * 2 + length;
|
|
VLA(uint8_t, packet, packet_size);
|
|
const uint16_t peer_number = net_htons(g->peer_number);
|
|
memcpy(packet, &peer_number, sizeof(uint16_t));
|
|
const uint16_t message_num = net_htons(g->lossy_message_number);
|
|
memcpy(packet + sizeof(uint16_t), &message_num, sizeof(uint16_t));
|
|
memcpy(packet + sizeof(uint16_t) * 2, data, length);
|
|
|
|
if (send_lossy_all_connections(g_c, g, packet, packet_size, -1) == 0) {
|
|
return -1;
|
|
}
|
|
|
|
++g->lossy_message_number;
|
|
return 0;
|
|
}
|
|
|
|
non_null()
|
|
static Message_Info *find_message_slot_or_reject(uint32_t message_number, uint8_t message_id, Group_Peer *peer)
|
|
{
|
|
const bool ignore_older = message_id == GROUP_MESSAGE_NAME_ID || message_id == GROUP_MESSAGE_TITLE_ID;
|
|
|
|
Message_Info *i;
|
|
|
|
for (i = peer->last_message_infos; i < peer->last_message_infos + peer->num_last_message_infos; ++i) {
|
|
if (message_number - (i->message_number + 1) <= ((uint32_t)1 << 31)) {
|
|
break;
|
|
}
|
|
|
|
if (message_number == i->message_number) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (ignore_older && message_id == i->message_id) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/** @brief Stores message info in `peer->last_message_infos`.
|
|
*
|
|
* @retval true if message should be processed.
|
|
* @retval false otherwise.
|
|
*/
|
|
non_null()
|
|
static bool check_message_info(uint32_t message_number, uint8_t message_id, Group_Peer *peer)
|
|
{
|
|
Message_Info *const i = find_message_slot_or_reject(message_number, message_id, peer);
|
|
|
|
if (i == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (i == peer->last_message_infos + MAX_LAST_MESSAGE_INFOS) {
|
|
return false;
|
|
}
|
|
|
|
if (peer->num_last_message_infos < MAX_LAST_MESSAGE_INFOS) {
|
|
++peer->num_last_message_infos;
|
|
}
|
|
|
|
memmove(i + 1, i, (&peer->last_message_infos[peer->num_last_message_infos - 1] - i) * sizeof(Message_Info));
|
|
|
|
i->message_number = message_number;
|
|
i->message_id = message_id;
|
|
|
|
return true;
|
|
}
|
|
|
|
non_null(1, 3) nullable(6)
|
|
static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length,
|
|
uint32_t connection_index, void *userdata)
|
|
{
|
|
if (length < sizeof(uint16_t) + sizeof(uint32_t) + 1) {
|
|
return;
|
|
}
|
|
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return;
|
|
}
|
|
|
|
uint16_t peer_number;
|
|
memcpy(&peer_number, data, sizeof(uint16_t));
|
|
peer_number = net_ntohs(peer_number);
|
|
|
|
uint32_t message_number;
|
|
memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number));
|
|
message_number = net_ntohl(message_number);
|
|
|
|
const uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)];
|
|
const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(message_number) + 1;
|
|
const uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1);
|
|
|
|
const bool ignore_frozen = message_id == GROUP_MESSAGE_FREEZE_PEER_ID;
|
|
|
|
const int index = ignore_frozen ? get_peer_index(g, peer_number)
|
|
: note_peer_active(g_c, groupnumber, peer_number, userdata);
|
|
|
|
if (index == -1) {
|
|
if (ignore_frozen) {
|
|
return;
|
|
}
|
|
|
|
if (g->connections[connection_index].type != GROUPCHAT_CONNECTION_ONLINE) {
|
|
return;
|
|
}
|
|
|
|
/* If we don't know the peer this packet came from, then we query the
|
|
* list of peers from the relaying peer.
|
|
* (They wouldn't have relayed it if they didn't know the peer.) */
|
|
send_peer_query(g_c, g->connections[connection_index].number, g->connections[connection_index].group_number);
|
|
return;
|
|
}
|
|
|
|
if (g->num_introducer_connections > 0 && count_connected(g) > DESIRED_CLOSEST) {
|
|
for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE
|
|
|| (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER) == 0
|
|
|| i == connection_index) {
|
|
continue;
|
|
}
|
|
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[i].number);
|
|
|
|
if (pk_equal(g->group[index].real_pk, real_pk)) {
|
|
/* Received message from peer relayed via another peer, so
|
|
* the introduction was successful */
|
|
remove_connection_reason(g_c, g, i, GROUPCHAT_CONNECTION_REASON_INTRODUCER);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!check_message_info(message_number, message_id, &g->group[index])) {
|
|
return;
|
|
}
|
|
|
|
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
|
get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[connection_index].number);
|
|
const bool direct_from_sender = pk_equal(g->group[index].real_pk, real_pk);
|
|
|
|
switch (message_id) {
|
|
case GROUP_MESSAGE_PING_ID: {
|
|
break;
|
|
}
|
|
|
|
case GROUP_MESSAGE_NEW_PEER_ID: {
|
|
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, true);
|
|
break;
|
|
}
|
|
|
|
case GROUP_MESSAGE_KILL_PEER_ID:
|
|
case GROUP_MESSAGE_FREEZE_PEER_ID: {
|
|
if (msg_data_len != GROUP_MESSAGE_KILL_PEER_LENGTH) {
|
|
return;
|
|
}
|
|
|
|
uint16_t kill_peer_number;
|
|
memcpy(&kill_peer_number, msg_data, sizeof(uint16_t));
|
|
kill_peer_number = net_ntohs(kill_peer_number);
|
|
|
|
if (peer_number == kill_peer_number) {
|
|
if (message_id == GROUP_MESSAGE_KILL_PEER_ID) {
|
|
delpeer(g_c, groupnumber, index, userdata);
|
|
} else {
|
|
freeze_peer(g_c, groupnumber, index, userdata);
|
|
}
|
|
} else {
|
|
return;
|
|
// TODO(irungentoo):
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case GROUP_MESSAGE_NAME_ID: {
|
|
if (!setnick(g_c, groupnumber, index, msg_data, msg_data_len, userdata, true)) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case GROUP_MESSAGE_TITLE_ID: {
|
|
if (!settitle(g_c, groupnumber, index, msg_data, msg_data_len, userdata)) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PACKET_ID_MESSAGE: {
|
|
if (msg_data_len == 0) {
|
|
return;
|
|
}
|
|
|
|
VLA(uint8_t, newmsg, msg_data_len + 1);
|
|
memcpy(newmsg, msg_data, msg_data_len);
|
|
newmsg[msg_data_len] = 0;
|
|
|
|
// TODO(irungentoo):
|
|
if (g_c->message_callback != nullptr) {
|
|
g_c->message_callback(g_c->m, groupnumber, index, 0, newmsg, msg_data_len, userdata);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PACKET_ID_ACTION: {
|
|
if (msg_data_len == 0) {
|
|
return;
|
|
}
|
|
|
|
VLA(uint8_t, newmsg, msg_data_len + 1);
|
|
memcpy(newmsg, msg_data, msg_data_len);
|
|
newmsg[msg_data_len] = 0;
|
|
|
|
// TODO(irungentoo):
|
|
if (g_c->message_callback != nullptr) {
|
|
g_c->message_callback(g_c->m, groupnumber, index, 1, newmsg, msg_data_len, userdata);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* If the packet was received from the peer who sent the message, relay it
|
|
* back. When the sender only has one group connection (e.g. because there
|
|
* are only two peers in the group), this is the only way for them to
|
|
* receive their own message. */
|
|
send_message_all_connections(g_c, g, data, length, direct_from_sender ? -1 : connection_index);
|
|
}
|
|
|
|
static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata)
|
|
{
|
|
Group_Chats *g_c = (Group_Chats *)object;
|
|
|
|
if (length < 1 + sizeof(uint16_t) + 1) {
|
|
return -1;
|
|
}
|
|
|
|
if (data[0] == PACKET_ID_ONLINE_PACKET) {
|
|
return handle_packet_online(g_c, friendcon_id, data + 1, length - 1);
|
|
}
|
|
|
|
if (data[0] == PACKET_ID_REJOIN_CONFERENCE) {
|
|
return handle_packet_rejoin(g_c, friendcon_id, data + 1, length - 1, userdata);
|
|
}
|
|
|
|
uint16_t groupnumber;
|
|
memcpy(&groupnumber, data + 1, sizeof(uint16_t));
|
|
groupnumber = net_ntohs(groupnumber);
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
const int index = friend_in_connections(g, friendcon_id);
|
|
|
|
if (index == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (data[0] == PACKET_ID_DIRECT_CONFERENCE) {
|
|
handle_direct_packet(g_c, groupnumber, data + 1 + sizeof(uint16_t),
|
|
length - (1 + sizeof(uint16_t)), index, userdata);
|
|
return 0;
|
|
}
|
|
|
|
if (data[0] == PACKET_ID_MESSAGE_CONFERENCE) {
|
|
handle_message_packet_group(g_c, groupnumber, data + 1 + sizeof(uint16_t),
|
|
length - (1 + sizeof(uint16_t)), index, userdata);
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/** @brief Did we already receive the lossy packet or not.
|
|
*
|
|
* @retval -1 on failure.
|
|
* @retval 0 if packet was not received.
|
|
* @retval 1 if packet was received.
|
|
*
|
|
* TODO(irungentoo): test this
|
|
*/
|
|
non_null()
|
|
static int lossy_packet_not_received(const Group_c *g, int peer_index, uint16_t message_number)
|
|
{
|
|
if (peer_index == -1) {
|
|
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;
|
|
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] != 0) {
|
|
return 1;
|
|
}
|
|
|
|
g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1;
|
|
return 0;
|
|
}
|
|
|
|
if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) > (1 << 15)) {
|
|
return -1;
|
|
}
|
|
|
|
const uint16_t top_distance = message_number - g->group[peer_index].top_lossy_number;
|
|
|
|
if (top_distance >= MAX_LOSSY_COUNT) {
|
|
crypto_memzero(g->group[peer_index].recv_lossy, sizeof(g->group[peer_index].recv_lossy));
|
|
} else { // top_distance < MAX_LOSSY_COUNT
|
|
for (unsigned int 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
/** Does this group type make use of lossy packets? */
|
|
static bool type_uses_lossy(uint8_t type)
|
|
{
|
|
return type == GROUPCHAT_TYPE_AV;
|
|
}
|
|
|
|
static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata)
|
|
{
|
|
const Group_Chats *g_c = (const Group_Chats *)object;
|
|
|
|
if (data[0] != PACKET_ID_LOSSY_CONFERENCE) {
|
|
return -1;
|
|
}
|
|
|
|
if (length < 1 + sizeof(uint16_t) * 3 + 1) {
|
|
return -1;
|
|
}
|
|
|
|
uint16_t groupnumber;
|
|
uint16_t peer_number;
|
|
uint16_t message_number;
|
|
memcpy(&groupnumber, data + 1, sizeof(uint16_t));
|
|
memcpy(&peer_number, 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);
|
|
message_number = net_ntohs(message_number);
|
|
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (!type_uses_lossy(g->type)) {
|
|
return -1;
|
|
}
|
|
|
|
const int index = friend_in_connections(g, friendcon_id);
|
|
|
|
if (index == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (peer_number == g->peer_number) {
|
|
return -1;
|
|
}
|
|
|
|
const int peer_index = get_peer_index(g, peer_number);
|
|
|
|
if (peer_index == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (lossy_packet_not_received(g, peer_index, message_number) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
const uint8_t *lossy_data = data + 1 + sizeof(uint16_t) * 3;
|
|
uint16_t lossy_length = length - (1 + sizeof(uint16_t) * 3);
|
|
const uint8_t message_id = lossy_data[0];
|
|
++lossy_data;
|
|
--lossy_length;
|
|
|
|
send_lossy_all_connections(g_c, g, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index);
|
|
|
|
if (g_c->lossy_packethandlers[message_id] == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (g_c->lossy_packethandlers[message_id](g->object, groupnumber, peer_index, g->group[peer_index].object,
|
|
lossy_data, lossy_length) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief Set the object that is tied to the group chat.
|
|
*
|
|
* @retval 0 on success.
|
|
* @retval -1 on failure
|
|
*/
|
|
int group_set_object(const Group_Chats *g_c, uint32_t groupnumber, void *object)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
g->object = object;
|
|
return 0;
|
|
}
|
|
|
|
/** @brief Set the object that is tied to the group peer.
|
|
*
|
|
* @retval 0 on success.
|
|
* @retval -1 on failure
|
|
*/
|
|
int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, void *object)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (peernumber >= g->numpeers) {
|
|
return -1;
|
|
}
|
|
|
|
g->group[peernumber].object = object;
|
|
return 0;
|
|
}
|
|
|
|
/** @brief Return the object tied to the group chat previously set by group_set_object.
|
|
*
|
|
* @retval NULL on failure.
|
|
* @return object on success.
|
|
*/
|
|
void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
return g->object;
|
|
}
|
|
|
|
/** @brief Return the object tied to the group chat peer previously set by group_peer_set_object.
|
|
*
|
|
* @retval NULL on failure.
|
|
* @return object on success.
|
|
*/
|
|
void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber)
|
|
{
|
|
const Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (peernumber >= g->numpeers) {
|
|
return nullptr;
|
|
}
|
|
|
|
return g->group[peernumber].object;
|
|
}
|
|
|
|
/** Interval in seconds to send ping messages */
|
|
#define GROUP_PING_INTERVAL 20
|
|
|
|
static bool ping_groupchat(const Group_Chats *g_c, uint32_t groupnumber)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (mono_time_is_timeout(g_c->mono_time, g->last_sent_ping, GROUP_PING_INTERVAL)) {
|
|
if (group_ping_send(g_c, groupnumber)) {
|
|
g->last_sent_ping = mono_time_get(g_c->mono_time);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Seconds of inactivity after which to freeze a peer */
|
|
#define FREEZE_TIMEOUT (GROUP_PING_INTERVAL * 3)
|
|
|
|
non_null(1) nullable(3)
|
|
static bool groupchat_freeze_timedout(Group_Chats *g_c, uint32_t groupnumber, void *userdata)
|
|
{
|
|
Group_c *g = get_group_c(g_c, groupnumber);
|
|
|
|
if (g == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < g->numpeers; ++i) {
|
|
if (g->group[i].peer_number == g->peer_number) {
|
|
continue;
|
|
}
|
|
|
|
if (mono_time_is_timeout(g_c->mono_time, g->group[i].last_active, FREEZE_TIMEOUT)) {
|
|
freeze_peer(g_c, groupnumber, i, userdata);
|
|
}
|
|
}
|
|
|
|
if (g->numpeers <= 1) {
|
|
g->title_fresh = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Push non-empty slots to start. */
|
|
non_null()
|
|
static void squash_connections(Group_c *g)
|
|
{
|
|
uint16_t num_connected = 0;
|
|
|
|
for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type != GROUPCHAT_CONNECTION_NONE) {
|
|
g->connections[num_connected] = g->connections[i];
|
|
++num_connected;
|
|
}
|
|
}
|
|
|
|
for (uint16_t i = num_connected; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
g->connections[i].type = GROUPCHAT_CONNECTION_NONE;
|
|
}
|
|
}
|
|
|
|
#define MIN_EMPTY_CONNECTIONS (1 + MAX_GROUP_CONNECTIONS / 10)
|
|
|
|
non_null()
|
|
static uint16_t empty_connection_count(const Group_c *g)
|
|
{
|
|
uint16_t to_clear = MIN_EMPTY_CONNECTIONS;
|
|
|
|
for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
|
|
if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) {
|
|
--to_clear;
|
|
|
|
if (to_clear == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return to_clear;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove old connections as necessary to ensure we have space for new
|
|
* connections.
|
|
*
|
|
* This invalidates connections array indices (which is
|
|
* why we do this periodically rather than on adding a connection).
|
|
*/
|
|
non_null()
|
|
static void clean_connections(Group_Chats *g_c, Group_c *g)
|
|
{
|
|
for (uint16_t to_clear = empty_connection_count(g); to_clear > 0; --to_clear) {
|
|
// Remove a connection. Prefer non-closest connections, and given
|
|
// that prefer non-online connections, and given that prefer earlier
|
|
// slots.
|
|
uint16_t i = 0;
|
|
|
|
while (i < MAX_GROUP_CONNECTIONS
|
|
&& (g->connections[i].type != GROUPCHAT_CONNECTION_CONNECTING
|
|
|| (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) != 0)) {
|
|
++i;
|
|
}
|
|
|
|
if (i == MAX_GROUP_CONNECTIONS) {
|
|
i = 0;
|
|
|
|
while (i < MAX_GROUP_CONNECTIONS - to_clear
|
|
&& (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE
|
|
|| (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) != 0)) {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
if (g->connections[i].type != GROUPCHAT_CONNECTION_NONE) {
|
|
remove_connection(g_c, g, i);
|
|
}
|
|
}
|
|
|
|
squash_connections(g);
|
|
}
|
|
|
|
/** Send current name (set in messenger) to all online groups. */
|
|
void send_name_all_groups(const Group_Chats *g_c)
|
|
{
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
Group_c *g = get_group_c(g_c, i);
|
|
|
|
if (g == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
if (g->status == GROUPCHAT_STATUS_CONNECTED) {
|
|
group_name_send(g_c, i, g_c->m->name, g_c->m->name_length);
|
|
g->need_send_name = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SAVED_PEER_SIZE_CONSTANT (2 * CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint16_t) + sizeof(uint64_t) + 1)
|
|
|
|
non_null()
|
|
static uint32_t saved_peer_size(const Group_Peer *peer)
|
|
{
|
|
return SAVED_PEER_SIZE_CONSTANT + peer->nick_len;
|
|
}
|
|
|
|
non_null()
|
|
static uint8_t *save_peer(const Group_Peer *peer, uint8_t *data)
|
|
{
|
|
memcpy(data, peer->real_pk, CRYPTO_PUBLIC_KEY_SIZE);
|
|
data += CRYPTO_PUBLIC_KEY_SIZE;
|
|
|
|
memcpy(data, peer->temp_pk, CRYPTO_PUBLIC_KEY_SIZE);
|
|
data += CRYPTO_PUBLIC_KEY_SIZE;
|
|
|
|
host_to_lendian_bytes16(data, peer->peer_number);
|
|
data += sizeof(uint16_t);
|
|
|
|
host_to_lendian_bytes64(data, peer->last_active);
|
|
data += sizeof(uint64_t);
|
|
|
|
// TODO(iphydf): This looks broken: nick_len can be > 255.
|
|
*data = peer->nick_len;
|
|
++data;
|
|
|
|
memcpy(data, peer->nick, peer->nick_len);
|
|
data += peer->nick_len;
|
|
|
|
return data;
|
|
}
|
|
|
|
#define SAVED_CONF_SIZE_CONSTANT (1 + GROUP_ID_LENGTH + sizeof(uint32_t) \
|
|
+ sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + 1)
|
|
|
|
non_null()
|
|
static uint32_t saved_conf_size(const Group_c *g)
|
|
{
|
|
uint32_t len = SAVED_CONF_SIZE_CONSTANT + g->title_len;
|
|
|
|
for (uint32_t j = 0; j < g->numpeers + g->numfrozen; ++j) {
|
|
const Group_Peer *peer = (j < g->numpeers) ? &g->group[j] : &g->frozen[j - g->numpeers];
|
|
|
|
if (pk_equal(peer->real_pk, g->real_pk)) {
|
|
continue;
|
|
}
|
|
|
|
len += saved_peer_size(peer);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* Save a future message number. The save will remain valid until we have sent
|
|
* this many more messages.
|
|
*/
|
|
#define SAVE_OFFSET_MESSAGE_NUMBER (1 << 16)
|
|
#define SAVE_OFFSET_LOSSY_MESSAGE_NUMBER (1 << 13)
|
|
|
|
non_null()
|
|
static uint8_t *save_conf(const Group_c *g, uint8_t *data)
|
|
{
|
|
*data = g->type;
|
|
++data;
|
|
|
|
memcpy(data, g->id, GROUP_ID_LENGTH);
|
|
data += GROUP_ID_LENGTH;
|
|
|
|
host_to_lendian_bytes32(data, g->message_number + SAVE_OFFSET_MESSAGE_NUMBER);
|
|
data += sizeof(uint32_t);
|
|
|
|
host_to_lendian_bytes16(data, g->lossy_message_number + SAVE_OFFSET_LOSSY_MESSAGE_NUMBER);
|
|
data += sizeof(uint16_t);
|
|
|
|
host_to_lendian_bytes16(data, g->peer_number);
|
|
data += sizeof(uint16_t);
|
|
|
|
uint8_t *const numsaved_location = data;
|
|
data += sizeof(uint32_t);
|
|
|
|
*data = g->title_len;
|
|
++data;
|
|
|
|
memcpy(data, g->title, g->title_len);
|
|
data += g->title_len;
|
|
|
|
uint32_t numsaved = 0;
|
|
|
|
for (uint32_t j = 0; j < g->numpeers + g->numfrozen; ++j) {
|
|
const Group_Peer *peer = (j < g->numpeers) ? &g->group[j] : &g->frozen[j - g->numpeers];
|
|
|
|
if (pk_equal(peer->real_pk, g->real_pk)) {
|
|
continue;
|
|
}
|
|
|
|
data = save_peer(peer, data);
|
|
++numsaved;
|
|
}
|
|
|
|
host_to_lendian_bytes32(numsaved_location, numsaved);
|
|
|
|
return data;
|
|
}
|
|
|
|
non_null()
|
|
static uint32_t conferences_section_size(const Group_Chats *g_c)
|
|
{
|
|
uint32_t len = 0;
|
|
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
const Group_c *g = get_group_c(g_c, i);
|
|
|
|
if (g == nullptr || g->status != GROUPCHAT_STATUS_CONNECTED) {
|
|
continue;
|
|
}
|
|
|
|
len += saved_conf_size(g);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
uint32_t conferences_size(const Group_Chats *g_c)
|
|
{
|
|
return 2 * sizeof(uint32_t) + conferences_section_size(g_c);
|
|
}
|
|
|
|
uint8_t *conferences_save(const Group_Chats *g_c, uint8_t *data)
|
|
{
|
|
const uint32_t len = conferences_section_size(g_c);
|
|
data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_CONFERENCES);
|
|
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
const Group_c *g = get_group_c(g_c, i);
|
|
|
|
if (g == nullptr || g->status != GROUPCHAT_STATUS_CONNECTED) {
|
|
continue;
|
|
}
|
|
|
|
data = save_conf(g, data);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @brief load_group Load a Group section from a save file
|
|
* @param g Group to load
|
|
* @param g_c Reference to all groupchats, need for utility functions
|
|
* @param data Start of the data to deserialze
|
|
* @param length Length of data
|
|
* @return 0 on error, number of consumed bytes otherwise
|
|
*/
|
|
non_null()
|
|
static uint32_t load_group(Group_c *g, const Group_Chats *g_c, const uint8_t *data, uint32_t length)
|
|
{
|
|
const uint8_t *init_data = data;
|
|
|
|
// Initialize to default values so we can unconditionally free in case of an error
|
|
setup_conference(g);
|
|
|
|
g->type = *data;
|
|
++data;
|
|
|
|
memcpy(g->id, data, GROUP_ID_LENGTH);
|
|
data += GROUP_ID_LENGTH;
|
|
|
|
lendian_bytes_to_host32(&g->message_number, data);
|
|
data += sizeof(uint32_t);
|
|
|
|
lendian_bytes_to_host16(&g->lossy_message_number, data);
|
|
data += sizeof(uint16_t);
|
|
|
|
lendian_bytes_to_host16(&g->peer_number, data);
|
|
data += sizeof(uint16_t);
|
|
|
|
lendian_bytes_to_host32(&g->numfrozen, data);
|
|
data += sizeof(uint32_t);
|
|
|
|
g->title_len = *data;
|
|
|
|
if (g->title_len > MAX_NAME_LENGTH) {
|
|
return 0;
|
|
}
|
|
|
|
++data;
|
|
|
|
assert((data - init_data) < UINT32_MAX);
|
|
|
|
if (length < (uint32_t)(data - init_data) + g->title_len) {
|
|
return 0;
|
|
}
|
|
|
|
memcpy(g->title, data, g->title_len);
|
|
data += g->title_len;
|
|
|
|
for (uint32_t j = 0; j < g->numfrozen; ++j) {
|
|
|
|
assert((data - init_data) < UINT32_MAX);
|
|
|
|
if (length < (uint32_t)(data - init_data) + SAVED_PEER_SIZE_CONSTANT) {
|
|
return 0;
|
|
}
|
|
|
|
// This is inefficient, but allows us to check data consistency before allocating memory
|
|
Group_Peer *tmp_frozen = (Group_Peer *)realloc(g->frozen, (j + 1) * sizeof(Group_Peer));
|
|
|
|
if (tmp_frozen == nullptr) {
|
|
// Memory allocation failure
|
|
return 0;
|
|
}
|
|
|
|
g->frozen = tmp_frozen;
|
|
|
|
Group_Peer *peer = &g->frozen[j];
|
|
*peer = empty_group_peer;
|
|
|
|
pk_copy(peer->real_pk, data);
|
|
data += CRYPTO_PUBLIC_KEY_SIZE;
|
|
pk_copy(peer->temp_pk, data);
|
|
data += CRYPTO_PUBLIC_KEY_SIZE;
|
|
|
|
lendian_bytes_to_host16(&peer->peer_number, data);
|
|
data += sizeof(uint16_t);
|
|
|
|
lendian_bytes_to_host64(&peer->last_active, data);
|
|
data += sizeof(uint64_t);
|
|
|
|
peer->nick_len = *data;
|
|
|
|
if (peer->nick_len > MAX_NAME_LENGTH) {
|
|
return 0;
|
|
}
|
|
|
|
++data;
|
|
assert((data - init_data) < UINT32_MAX);
|
|
|
|
if (length < (uint32_t)(data - init_data) + peer->nick_len) {
|
|
return 0;
|
|
}
|
|
|
|
memcpy(peer->nick, data, peer->nick_len);
|
|
data += peer->nick_len;
|
|
|
|
// NOTE: this relies on friends being loaded before conferences.
|
|
peer->is_friend = getfriend_id(g_c->m, peer->real_pk) != -1;
|
|
}
|
|
|
|
if (g->numfrozen > g->maxfrozen) {
|
|
g->maxfrozen = g->numfrozen;
|
|
}
|
|
|
|
g->status = GROUPCHAT_STATUS_CONNECTED;
|
|
|
|
pk_copy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto));
|
|
|
|
assert((data - init_data) < UINT32_MAX);
|
|
|
|
return (uint32_t)(data - init_data);
|
|
}
|
|
|
|
non_null()
|
|
static State_Load_Status load_conferences_helper(Group_Chats *g_c, const uint8_t *data, uint32_t length)
|
|
{
|
|
const uint8_t *init_data = data;
|
|
|
|
while (length >= (uint32_t)(data - init_data) + SAVED_CONF_SIZE_CONSTANT) {
|
|
const int groupnumber = create_group_chat(g_c);
|
|
|
|
// Helpful for testing
|
|
assert(groupnumber != -1);
|
|
|
|
if (groupnumber == -1) {
|
|
// If this fails there's a serious problem, don't bother with cleanup
|
|
LOGGER_ERROR(g_c->m->log, "conference creation failed");
|
|
return STATE_LOAD_STATUS_ERROR;
|
|
}
|
|
|
|
Group_c *g = &g_c->chats[groupnumber];
|
|
|
|
const uint32_t consumed = load_group(g, g_c, data, length - (uint32_t)(data - init_data));
|
|
|
|
if (consumed == 0) {
|
|
// remove partially loaded stuff, wipe_group_chat must be able to wipe a partially loaded group
|
|
const bool ret = wipe_group_chat(g_c, groupnumber);
|
|
|
|
// HACK: suppress unused variable warning
|
|
if (!ret) {
|
|
// wipe_group_chat(...) must be able to wipe partially allocated groups
|
|
assert(ret);
|
|
}
|
|
|
|
LOGGER_ERROR(g_c->m->log, "conference loading failed");
|
|
return STATE_LOAD_STATUS_ERROR;
|
|
}
|
|
|
|
data += consumed;
|
|
|
|
const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), g->peer_number,
|
|
nullptr, true, false);
|
|
|
|
if (peer_index == -1) {
|
|
LOGGER_ERROR(g_c->m->log, "adding peer %d failed", g->peer_number);
|
|
return STATE_LOAD_STATUS_ERROR;
|
|
}
|
|
|
|
setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, nullptr, false);
|
|
}
|
|
|
|
return STATE_LOAD_STATUS_CONTINUE;
|
|
}
|
|
|
|
non_null()
|
|
static State_Load_Status load_conferences(Group_Chats *g_c, const uint8_t *data, uint32_t length)
|
|
{
|
|
const State_Load_Status res = load_conferences_helper(g_c, data, length);
|
|
|
|
if (res == STATE_LOAD_STATUS_CONTINUE) {
|
|
return res;
|
|
}
|
|
|
|
// Loading failed, cleanup all Group_c
|
|
|
|
// save locally, because wipe_group_chat(...) modifies it
|
|
const uint16_t num_groups = g_c->num_chats;
|
|
|
|
for (uint16_t i = 0; i < num_groups; ++i) {
|
|
wipe_group_chat(g_c, i);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool conferences_load_state_section(Group_Chats *g_c, const uint8_t *data, uint32_t length, uint16_t type,
|
|
State_Load_Status *status)
|
|
{
|
|
if (type != STATE_TYPE_CONFERENCES) {
|
|
return false;
|
|
}
|
|
|
|
*status = load_conferences(g_c, data, length);
|
|
return true;
|
|
}
|
|
|
|
/** Create new groupchat instance. */
|
|
Group_Chats *new_groupchats(const Mono_Time *mono_time, Messenger *m)
|
|
{
|
|
if (m == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
Group_Chats *temp = (Group_Chats *)calloc(1, sizeof(Group_Chats));
|
|
|
|
if (temp == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
temp->mono_time = mono_time;
|
|
temp->m = m;
|
|
temp->fr_c = m->fr_c;
|
|
m_callback_conference_invite(m, &handle_friend_invite_packet);
|
|
|
|
set_global_status_callback(m->fr_c, &g_handle_any_status, temp);
|
|
|
|
return temp;
|
|
}
|
|
|
|
/** main groupchats loop. */
|
|
void do_groupchats(Group_Chats *g_c, void *userdata)
|
|
{
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
Group_c *g = get_group_c(g_c, i);
|
|
|
|
if (g == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
if (g->status == GROUPCHAT_STATUS_CONNECTED) {
|
|
connect_to_closest(g_c, i, userdata);
|
|
ping_groupchat(g_c, i);
|
|
groupchat_freeze_timedout(g_c, i, userdata);
|
|
clean_connections(g_c, g);
|
|
|
|
if (g->need_send_name) {
|
|
group_name_send(g_c, i, g_c->m->name, g_c->m->name_length);
|
|
g->need_send_name = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(irungentoo):
|
|
}
|
|
|
|
/** Free everything related with group chats. */
|
|
void kill_groupchats(Group_Chats *g_c)
|
|
{
|
|
if (g_c == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
del_groupchat(g_c, i, false);
|
|
}
|
|
|
|
m_callback_conference_invite(g_c->m, nullptr);
|
|
set_global_status_callback(g_c->m->fr_c, nullptr, nullptr);
|
|
g_c->m->conferences_object = nullptr;
|
|
free(g_c);
|
|
}
|
|
|
|
/**
|
|
* @brief 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(const Group_Chats *g_c)
|
|
{
|
|
uint32_t ret = 0;
|
|
|
|
for (uint16_t i = 0; i < g_c->num_chats; ++i) {
|
|
if (g_c->chats[i].status != GROUPCHAT_STATUS_NONE) {
|
|
++ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** @brief 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(const Group_Chats *g_c, uint32_t *out_list, uint32_t list_size)
|
|
{
|
|
if (out_list == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
if (g_c->num_chats == 0) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t ret = 0;
|
|
|
|
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) {
|
|
out_list[ret] = i;
|
|
++ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|