Add mechanism for recovering from disconnections in conferences

* add freezing and unfreezing of peers
  * add rejoin packet
  * revise handling of temporary invited connections
  * rename "peer kill" packet to "peer leave" packet
  * test rejoining in conference test
  * use custom clock in conference test
This commit is contained in:
zugz 2018-07-25 08:43:48 +01:00 committed by zugz (tox)
parent 6872c14e1a
commit 1b2322284f
No known key found for this signature in database
GPG Key ID: 6F2BDA289D04F249
15 changed files with 773 additions and 277 deletions

View File

@ -81,6 +81,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(2, conference_double_invite_test);
run_auto_test(2, conference_double_invite_test, false);
return 0;
}

View File

@ -129,6 +129,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(2, conference_peer_nick_test);
run_auto_test(2, conference_peer_nick_test, false);
return 0;
}

View File

@ -16,26 +16,31 @@
#include "check_compat.h"
#define NUM_GROUP_TOX 16
#define NUM_DISCONNECT 8
#define GROUP_MESSAGE "Install Gentoo"
#define NAME_FORMAT_STR "Tox #%4u"
#define NAMELEN 9
#define NAME_FORMAT "%9s"
#define NAME_FORMAT_STR "Tox #%4u"
#define NEW_NAME_FORMAT_STR "New #%4u"
typedef struct State {
uint32_t id;
uint32_t index;
uint64_t clock;
bool invited_next;
} State;
#include "run_auto_test.h"
static void handle_self_connection_status(
Tox *tox, TOX_CONNECTION connection_status, void *user_data)
{
const State *state = (State *)user_data;
if (connection_status != TOX_CONNECTION_NONE) {
printf("tox #%u: is now connected\n", state->id);
printf("tox #%u: is now connected\n", state->index);
} else {
printf("tox #%u: is now disconnected\n", state->id);
printf("tox #%u: is now disconnected\n", state->index);
}
}
@ -45,9 +50,9 @@ static void handle_friend_connection_status(
const State *state = (State *)user_data;
if (connection_status != TOX_CONNECTION_NONE) {
printf("tox #%u: is now connected to friend %u\n", state->id, friendnumber);
printf("tox #%u: is now connected to friend %u\n", state->index, friendnumber);
} else {
printf("tox #%u: is now disconnected from friend %u\n", state->id, friendnumber);
printf("tox #%u: is now disconnected from friend %u\n", state->index, friendnumber);
}
}
@ -56,18 +61,18 @@ static void handle_conference_invite(
const uint8_t *data, size_t length, void *user_data)
{
const State *state = (State *)user_data;
ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %d", state->id, type);
ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %d", state->index, type);
TOX_ERR_CONFERENCE_JOIN err;
uint32_t g_num = tox_conference_join(tox, friendnumber, data, length, &err);
ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%u: error joining group: %d", state->id, err);
ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", state->id);
ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%u: error joining group: %d", state->index, err);
ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", state->index);
// Try joining again. We should only be allowed to join once.
tox_conference_join(tox, friendnumber, data, length, &err);
ck_assert_msg(err != TOX_ERR_CONFERENCE_JOIN_OK,
"tox #%u: joining groupchat twice should be impossible.", state->id);
"tox #%u: joining groupchat twice should be impossible.", state->index);
}
static void handle_conference_connected(
@ -81,8 +86,8 @@ static void handle_conference_connected(
TOX_ERR_CONFERENCE_INVITE err;
tox_conference_invite(tox, 1, 0, &err);
ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %d", state->id, err);
printf("tox #%u: invited next friend\n", state->id);
ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %d", state->index, err);
printf("tox #%u: invited next friend\n", state->index);
state->invited_next = true;
}
@ -97,8 +102,93 @@ static void handle_conference_message(
}
}
static bool toxes_are_disconnected_from_group(uint32_t tox_count, Tox **toxes, int disconnected_count,
bool *disconnected)
{
for (uint32_t i = 0; i < tox_count; i++) {
if (disconnected[i]) {
continue;
}
if (tox_conference_peer_count(toxes[i], 0, nullptr) > tox_count - NUM_DISCONNECT) {
return false;
}
}
return true;
}
static bool all_connected_to_group(uint32_t tox_count, Tox **toxes)
{
for (uint32_t i = 0; i < tox_count; i++) {
if (tox_conference_peer_count(toxes[i], 0, nullptr) < tox_count) {
return false;
}
}
return true;
}
/* returns a random index at which a list of booleans is false
* (some such index is required to exist)
* */
static uint32_t random_false_index(bool *list, const uint32_t length)
{
uint32_t index;
do {
index = random_u32() % length;
} while (list[index]);
return index;
}
static void run_conference_tests(Tox **toxes, State *state)
{
/* disabling name propagation check for now, as it occasionally fails due
* to disconnections too short to trigger freezing */
const bool check_name_propagation = false;
printf("letting random toxes timeout\n");
bool disconnected[NUM_GROUP_TOX] = {0};
ck_assert(NUM_DISCONNECT < NUM_GROUP_TOX);
for (uint16_t i = 0; i < NUM_DISCONNECT; ++i) {
uint32_t disconnect = random_false_index(disconnected, NUM_GROUP_TOX);
disconnected[disconnect] = true;
printf("Disconnecting #%u\n", state[disconnect].index);
}
do {
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
if (!disconnected[i]) {
tox_iterate(toxes[i], &state[i]);
state[i].clock += 1000;
}
}
c_sleep(20);
} while (!toxes_are_disconnected_from_group(NUM_GROUP_TOX, toxes, NUM_DISCONNECT, disconnected));
if (check_name_propagation) {
printf("changing names\n");
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
char name[NAMELEN + 1];
snprintf(name, NAMELEN + 1, NEW_NAME_FORMAT_STR, state[i].index);
tox_self_set_name(toxes[i], (const uint8_t *)name, NAMELEN, nullptr);
}
}
printf("reconnecting toxes\n");
do {
iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
} while (!all_connected_to_group(NUM_GROUP_TOX, toxes));
printf("running conference tests\n");
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
tox_callback_conference_message(toxes[i], &handle_conference_message);
}
@ -112,41 +202,33 @@ static void run_conference_tests(Tox **toxes, State *state)
err == TOX_ERR_CONFERENCE_SEND_MESSAGE_OK, "failed to send group message");
num_recv = 0;
for (uint8_t j = 0; j < 20; ++j) {
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i], &state[i]);
for (uint8_t j = 0; j < NUM_GROUP_TOX * 2; ++j) {
iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
}
c_sleep(25);
}
c_sleep(25);
ck_assert_msg(num_recv == NUM_GROUP_TOX, "failed to recv group messages");
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
for (uint16_t j = 0; j < NUM_GROUP_TOX; ++j) {
const size_t len = tox_conference_peer_get_name_size(toxes[i], 0, j, nullptr);
ck_assert_msg(len == NAMELEN, "name of #%u according to #%u has incorrect length %u", state[j].id, state[i].id,
ck_assert_msg(len == NAMELEN, "name of #%u according to #%u has incorrect length %u", state[j].index, state[i].index,
(unsigned int)len);
if (check_name_propagation) {
uint8_t name[NAMELEN];
tox_conference_peer_get_name(toxes[i], 0, j, name, nullptr);
char expected_name[NAMELEN + 1];
snprintf(expected_name, NAMELEN + 1, NAME_FORMAT_STR, state[j].id);
ck_assert_msg(memcmp(name, expected_name, NAMELEN) == 0,
"name of #%u according to #%u is \"" NAME_FORMAT "\"; expected \"%s\"",
state[j].id, state[i].id, name, expected_name);
/* Note the toxes will have been reordered */
ck_assert_msg(memcmp(name, "New", 3) == 0,
"name of #%u according to #%u not updated", state[j].index, state[i].index);
}
}
}
for (uint16_t k = NUM_GROUP_TOX; k != 0 ; --k) {
tox_conference_delete(toxes[k - 1], 0, nullptr);
for (uint8_t j = 0; j < 10; ++j) {
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i], &state[i]);
}
c_sleep(50);
for (uint8_t j = 0; j < 10 || j < NUM_GROUP_TOX; ++j) {
iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
}
for (uint16_t i = 0; i < k - 1; ++i) {
@ -158,82 +240,23 @@ static void run_conference_tests(Tox **toxes, State *state)
}
}
static void test_many_group(void)
static void test_many_group(Tox **toxes, State *state)
{
const time_t test_start_time = time(nullptr);
Tox *toxes[NUM_GROUP_TOX];
State state[NUM_GROUP_TOX];
memset(state, 0, NUM_GROUP_TOX * sizeof(State));
time_t cur_time = time(nullptr);
struct Tox_Options *opts = tox_options_new(nullptr);
tox_options_set_start_port(opts, 33445);
tox_options_set_end_port(opts, 34445);
printf("creating %d toxes\n", NUM_GROUP_TOX);
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
TOX_ERR_NEW err;
state[i].id = i + 1;
toxes[i] = tox_new_log(opts, &err, &state[i]);
ck_assert_msg(toxes[i] != nullptr, "failed to create tox instance %u: error %d", i, err);
tox_callback_self_connection_status(toxes[i], &handle_self_connection_status);
tox_callback_friend_connection_status(toxes[i], &handle_friend_connection_status);
tox_callback_conference_invite(toxes[i], &handle_conference_invite);
tox_callback_conference_connected(toxes[i], &handle_conference_connected);
char name[NAMELEN + 1];
snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, state[i].id);
snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, state[i].index);
tox_self_set_name(toxes[i], (const uint8_t *)name, NAMELEN, nullptr);
if (i != 0) {
uint8_t dht_key[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(toxes[0], dht_key);
const uint16_t dht_port = tox_self_get_udp_port(toxes[0], nullptr);
tox_bootstrap(toxes[i], "localhost", dht_port, dht_key, nullptr);
}
}
tox_options_free(opts);
printf("creating a chain of friends\n");
for (unsigned i = 1; i < NUM_GROUP_TOX; ++i) {
TOX_ERR_FRIEND_ADD err;
uint8_t key[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(toxes[i - 1], key);
tox_friend_add_norequest(toxes[i], key, &err);
ck_assert_msg(err == TOX_ERR_FRIEND_ADD_OK, "failed to add friend: error %d", err);
tox_self_get_public_key(toxes[i], key);
tox_friend_add_norequest(toxes[i - 1], key, &err);
ck_assert_msg(err == TOX_ERR_FRIEND_ADD_OK, "failed to add friend: error %d", err);
}
printf("waiting for everyone to come online\n");
unsigned online_count = 0;
do {
online_count = 0;
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i], &state[i]);
online_count += tox_friend_get_connection_status(toxes[i], 0, nullptr) != TOX_CONNECTION_NONE;
}
printf("currently %u toxes are online\n", online_count);
fflush(stdout);
c_sleep(1000);
} while (online_count != NUM_GROUP_TOX);
printf("friends connected, took %d seconds\n", (int)(time(nullptr) - cur_time));
ck_assert_msg(tox_conference_new(toxes[0], nullptr) != UINT32_MAX, "failed to create group");
printf("tox #%u: inviting its first friend\n", state[0].id);
printf("tox #%u: inviting its first friend\n", state[0].index);
ck_assert_msg(tox_conference_invite(toxes[0], 0, 0, nullptr) != 0, "failed to invite friend");
state[0].invited_next = true;
ck_assert_msg(tox_conference_set_title(toxes[0], 0, (const uint8_t *)"Gentoo", sizeof("Gentoo") - 1, nullptr) != 0,
@ -244,17 +267,16 @@ static void test_many_group(void)
uint16_t invited_count = 0;
do {
iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
invited_count = 0;
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i], &state[i]);
invited_count += state[i].invited_next;
}
c_sleep(50);
} while (invited_count != NUM_GROUP_TOX - 1);
cur_time = time(nullptr);
uint64_t pregroup_clock = state[0].clock;
printf("waiting for all toxes to be in the group\n");
uint16_t fully_connected_count = 0;
@ -262,8 +284,9 @@ static void test_many_group(void)
fully_connected_count = 0;
printf("current peer counts: [");
iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i], &state[i]);
TOX_ERR_CONFERENCE_PEER_QUERY err;
uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, &err);
@ -282,8 +305,6 @@ static void test_many_group(void)
printf("]\n");
fflush(stdout);
c_sleep(200);
} while (fully_connected_count != NUM_GROUP_TOX);
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
@ -300,16 +321,10 @@ static void test_many_group(void)
ck_assert_msg(memcmp("Gentoo", title, ret) == 0, "Wrong title");
}
printf("group connected, took %d seconds\n", (int)(time(nullptr) - cur_time));
printf("group connected, took %d seconds\n", (int)((state[0].clock - pregroup_clock) / 1000));
run_conference_tests(toxes, state);
printf("tearing down toxes\n");
for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) {
tox_kill(toxes[i]);
}
printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time));
}
@ -317,6 +332,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
test_many_group();
run_auto_test(NUM_GROUP_TOX, test_many_group, true);
return 0;
}

View File

@ -27,6 +27,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(2, friend_connection_test);
run_auto_test(2, friend_connection_test, false);
return 0;
}

View File

@ -61,6 +61,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(2, test_lossless_packet);
run_auto_test(2, test_lossless_packet, false);
return 0;
}

View File

@ -57,6 +57,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(2, test_lossy_packet);
run_auto_test(2, test_lossy_packet, false);
return 0;
}

View File

@ -62,6 +62,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(3, net_crypto_overflow_test);
run_auto_test(3, net_crypto_overflow_test, false);
return 0;
}

View File

@ -47,6 +47,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(2, net_crypto_overflow_test);
run_auto_test(2, net_crypto_overflow_test, false);
return 0;
}

View File

@ -101,6 +101,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(TOX_COUNT, test_reconnect);
run_auto_test(TOX_COUNT, test_reconnect, false);
return 0;
}

View File

@ -48,7 +48,7 @@ static uint64_t get_state_clock_callback(Mono_Time *mono_time, void *user_data)
return state->clock;
}
static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *state))
static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *state), bool chain)
{
printf("initialising %u toxes\n", tox_count);
Tox **toxes = (Tox **)calloc(tox_count, sizeof(Tox *));
@ -66,6 +66,21 @@ static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *stat
mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &state[i]);
}
if (chain) {
printf("each tox adds adjacent toxes as friends\n");
for (uint32_t i = 0; i < tox_count; i++) {
for (uint32_t j = i - 1; j != i + 3; j += 2) {
if (j >= tox_count) {
continue;
}
uint8_t public_key[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(toxes[j], public_key);
tox_friend_add_norequest(toxes[i], public_key, nullptr);
}
}
} else {
printf("toxes all add each other as friends\n");
for (uint32_t i = 0; i < tox_count; i++) {
@ -77,7 +92,7 @@ static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *stat
}
}
}
}
printf("bootstrapping all toxes off toxes[0]\n");
uint8_t dht_key[TOX_PUBLIC_KEY_SIZE];

View File

@ -61,6 +61,6 @@ int main(void)
{
setvbuf(stdout, nullptr, _IONBF, 0);
run_auto_test(2, send_message_test);
run_auto_test(2, send_message_test, false);
return 0;
}

128
docs/minpgc.md Normal file
View File

@ -0,0 +1,128 @@
# Persistent conferences
This document describes the "minpgc" simple persistent conferences
implementation of PR #1069.
Many of the ideas derive from isotoxin's persistent conferences
implementation, PR #826.
## Specification of changes from pre-existing conference specification
We add one new packet type:
Rejoin Conference packet
| Length | Contents |
|:-------|:--------------------------------|
| `1` | `uint8_t` (0x64) |
| `33` | Group chat identifier |
A peer times out from a group if it has been inactive for 60s. When a peer
times out, we flag it as _frozen_. Frozen peers are disregarded for all
purposes except those discussed below - in particular no packets are sent to
them except as described below, they are omitted from the peer lists sent to
the client or in a Peer Response packet, and they are not considered when
determining closest peers for establishing direct connections.
A peer is considered to be active if we receive a group message or Rejoin
packet from it, or a New Peer message for it.
If a frozen peer is seen to be active, we remove its 'frozen' flag and send a
Name group message. (We can hold off on sending this message until the next
tox\_iterate, and only send one message if many frozen peers become active at
once).
If we receive a New Peer message for a peer, we update its DHT pubkey.
If we receive a group message originating from an unknown peer, we drop the
message but send a Peer Query packet back to the peer who directly sent us the
message. (This is current behaviour; it's mentioned here because it's important
and not currently mentioned in the spec.)
If we receive a Rejoin packet from a peer we update its DHT pubkey, add a
temporary groupchat connection for the peer, and, once the connection is
online, send out a New Peer message announcing the peer, and a Name message.
Whenever we make a new friend connection, we check if the public key is that
of any frozen peer. If so, we send it a Rejoin packet, add a temporary
groupchat connection for it, and, once the connection is online, send the
peer a Peer Query packet.
We do the same with a peer when we are setting it as frozen if we have a
friend connection to it.
The temporary groupchat connections established in sending and handling Rejoin
packets are not immediately operational (because group numbers are not known);
rather, an Online packet is sent when we handle a Rejoin packet.
When a connection is set as online as a result of an Online packet, we ping
the group.
When processing the reply to a Peer Query, we update the DHT pubkey of an
existing peer if and only if it is frozen or has not had its DHT pubkey
updated since it last stopped being frozen.
When we receive a Title Response packet, we set the title if it has never been
set or if at some point since it was last set, there were no unfrozen peers
(except us).
## Discussion
### Overview
The intention is to recover seamlessly from splits in the group, the most
common form of which is a single peer temporarily losing all connectivity.
To see how this works, first note that groups (even before the changes
discussed here) have the property that for a group to be connected in the
sense that any peer will receive the messages of any other peer and have them
in their peerlist, it is necessary and sufficient that there is a path of
direct group connections between any two peers.
Now suppose the group is split into two connected components, with each member
of one component frozen according to the members of the other. Suppose there
are two peers, one in each component, which are using the above protocol, and
suppose they establish a friend connection. Then each will rejoin the other,
forming a direct group connection. Hence the whole group will become connected
(even if all other peers are using the unmodified protocol).
The Peer Query packet sent on rejoining hastens this process.
Peers who leave the group during a split will not be deleted by all peers
after the merge - but they will be set as frozen due to ping timeouts, which
is sufficient.
### Titles
If we have a split into components each containing multiple peers, and the
title is changed in one component, then peers will continue to disagree on the
title after the split. Short of a complicated voting system, this seems the
only reasonable behaviour.
### Implementation notes
Although I've described the logic in terms of an 'frozen' flag, it might
actually make more sense in the implementation to have a separate list for
frozen peers.
## Saving
Saving could be implemented by simply saving all live groups with their group
numbers and full peer info for all peers. On reload, all peers would be set as
frozen.
The client would need to support this by understanding that these groups exist
on start-up (e.g. starting windows for them), and by not automatically killing
groups on closing the client.
## Limitations
If a peer disconnects from the group for a period short enough that group
timeouts do not occur, and a name change occurs during this period, then the
name change will never be propagated.
One way to deal with this would be a general mechanism for storing and
requesting missed group messages. But this is considered out of scope of this
PR.
If a peer changes its DHT pubkey, the change might not be properly propagated
under various circumstances - in particular, if connections do not go down
long enough for the peer to become frozen.
One way to deal with this would be to add a group message announcing the
sending peer's current DHT pubkey, and treat it analogously to the Name
message.

File diff suppressed because it is too large Load Diff

View File

@ -49,8 +49,9 @@ typedef struct Message_Info {
typedef struct Group_Peer {
uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE];
uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE];
bool temp_pk_updated;
uint64_t last_recv;
uint64_t last_active;
Message_Info
last_message_infos[MAX_LAST_MESSAGE_INFOS]; /* received messages, strictly decreasing in message_number */
@ -79,11 +80,18 @@ typedef enum Groupchat_Close_Type {
GROUPCHAT_CLOSE_ONLINE
} Groupchat_Close_Type;
/* Connection is to one of the closest DESIRED_CLOSE_CONNECTIONS peers */
#define GROUPCHAT_CLOSE_REASON_CLOSEST (1 << 0)
/* Connection is to a peer we are introducing to the conference */
#define GROUPCHAT_CLOSE_REASON_INTRODUCING (1 << 1)
/* Connection is to a peer who is introducing us to the conference */
#define GROUPCHAT_CLOSE_REASON_INTRODUCER (1 << 2)
typedef struct Groupchat_Close {
uint8_t type; /* GROUPCHAT_CLOSE_* */
bool closest; /* connected to peer because it is one of our closest peers */
bool introducer; /* connected to peer because it introduced us to the group */
bool introduced; /* connected to peer because we introduced it to the group */
uint8_t reasons; /* bit field with flags GROUPCHAT_CLOSE_REASON_* */
uint32_t number;
uint16_t group_number;
} Groupchat_Close;
@ -101,9 +109,15 @@ typedef void group_on_delete_cb(void *object, uint32_t conference_number);
typedef struct Group_c {
uint8_t status;
bool need_send_name;
bool title_fresh;
Group_Peer *group;
uint32_t numpeers;
Group_Peer *frozen;
uint32_t numfrozen;
/* TODO(zugz) rename close to something more accurate - "connected"? */
Groupchat_Close close[MAX_GROUP_CONNECTIONS];
@ -123,7 +137,7 @@ typedef struct Group_c {
uint64_t last_sent_ping;
int number_joined; /* friendcon_id of person that invited us to the chat. (-1 means none) */
uint32_t num_introducer_connections;
void *object;

View File

@ -78,6 +78,7 @@
#define PACKET_ID_ONLINE_PACKET 97
#define PACKET_ID_DIRECT_CONFERENCE 98
#define PACKET_ID_MESSAGE_CONFERENCE 99
#define PACKET_ID_REJOIN_CONFERENCE 100
#define PACKET_ID_LOSSY_CONFERENCE 199
/*** Crypto connections. ***/