diff options
author | zugz <mbays+tox@sdf.org> | 2018-07-25 08:46:28 +0100 |
---|---|---|
committer | zugz (tox) <mbays+tox@sdf.org> | 2018-08-02 22:03:18 +0100 |
commit | aa63c1330c49ee1c4b675037e2e54516950203a2 (patch) | |
tree | d9064b106cd62e7c7fbc212c4dbaefc0298d977b | |
parent | 3a4987da18b165d5a5297ddaf64f1336958bb3c7 (diff) |
Fix problems with initial connections and name-setting in conferences
* test names in conference_test
* raise error on attempt to invite friend to group before we are connected
* revise handling of temporary invited connections
We are now careful not to prematurely delete a connection to a peer
established during the invitation process; namely, before we have sufficient
other connections and have confirmed that we have an alternative route to the
peer.
* process out-of-order messages from a peer
* don't reset names when handling a Peer Response
-rw-r--r-- | auto_tests/conference_simple_test.c | 34 | ||||
-rw-r--r-- | auto_tests/conference_test.c | 141 | ||||
-rw-r--r-- | toxcore/group.c | 141 | ||||
-rw-r--r-- | toxcore/group.h | 18 | ||||
-rw-r--r-- | toxcore/tox.api.h | 4 | ||||
-rw-r--r-- | toxcore/tox.c | 4 | ||||
-rw-r--r-- | toxcore/tox.h | 5 |
7 files changed, 260 insertions, 87 deletions
diff --git a/auto_tests/conference_simple_test.c b/auto_tests/conference_simple_test.c index 8d95bba8..f2bede30 100644 --- a/auto_tests/conference_simple_test.c +++ b/auto_tests/conference_simple_test.c | |||
@@ -13,6 +13,7 @@ typedef struct State { | |||
13 | uint32_t id; | 13 | uint32_t id; |
14 | bool self_online; | 14 | bool self_online; |
15 | bool friend_online; | 15 | bool friend_online; |
16 | bool invited_next; | ||
16 | 17 | ||
17 | bool joined; | 18 | bool joined; |
18 | uint32_t conference; | 19 | uint32_t conference; |
@@ -55,19 +56,6 @@ static void handle_conference_invite(Tox *tox, uint32_t friend_number, TOX_CONFE | |||
55 | fprintf(stderr, "tox%d Joined conference %d\n", state->id, state->conference); | 56 | fprintf(stderr, "tox%d Joined conference %d\n", state->id, state->conference); |
56 | state->joined = true; | 57 | state->joined = true; |
57 | } | 58 | } |
58 | |||
59 | // We're tox2, so now we invite tox3. | ||
60 | if (state->id == 2) { | ||
61 | TOX_ERR_CONFERENCE_INVITE err; | ||
62 | tox_conference_invite(tox, 1, state->conference, &err); | ||
63 | |||
64 | if (err != TOX_ERR_CONFERENCE_INVITE_OK) { | ||
65 | fprintf(stderr, "ERROR: %d\n", err); | ||
66 | exit(EXIT_FAILURE); | ||
67 | } | ||
68 | |||
69 | fprintf(stderr, "tox2 invited tox3\n"); | ||
70 | } | ||
71 | } | 59 | } |
72 | 60 | ||
73 | static void handle_conference_message(Tox *tox, uint32_t conference_number, uint32_t peer_number, | 61 | static void handle_conference_message(Tox *tox, uint32_t conference_number, uint32_t peer_number, |
@@ -99,6 +87,25 @@ static void handle_conference_peer_list_changed(Tox *tox, uint32_t conference_nu | |||
99 | 87 | ||
100 | fprintf(stderr, "tox%d has %d peers online\n", state->id, count); | 88 | fprintf(stderr, "tox%d has %d peers online\n", state->id, count); |
101 | state->peers = count; | 89 | state->peers = count; |
90 | |||
91 | // We're tox2, so now we invite tox3. | ||
92 | if (state->id == 2 && !state->invited_next) { | ||
93 | // TODO(zugz): neater way to determine whether we are connected, and when | ||
94 | // we become so | ||
95 | TOX_ERR_CONFERENCE_PEER_QUERY peer_err; | ||
96 | tox_conference_peer_number_is_ours(tox, 0, 0, &peer_err); | ||
97 | |||
98 | if (peer_err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { | ||
99 | return; | ||
100 | } | ||
101 | |||
102 | TOX_ERR_CONFERENCE_INVITE err; | ||
103 | tox_conference_invite(tox, 1, state->conference, &err); | ||
104 | ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox2 failed to invite tox3: err = %d", err); | ||
105 | |||
106 | state->invited_next = true; | ||
107 | fprintf(stderr, "tox2 invited tox3\n"); | ||
108 | } | ||
102 | } | 109 | } |
103 | 110 | ||
104 | int main(void) | 111 | int main(void) |
@@ -195,6 +202,7 @@ int main(void) | |||
195 | TOX_ERR_CONFERENCE_INVITE err; | 202 | TOX_ERR_CONFERENCE_INVITE err; |
196 | tox_conference_invite(tox1, 0, state1.conference, &err); | 203 | tox_conference_invite(tox1, 0, state1.conference, &err); |
197 | ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "failed to invite a friend: err = %d", err); | 204 | ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "failed to invite a friend: err = %d", err); |
205 | state1.invited_next = true; | ||
198 | fprintf(stderr, "tox1 invited tox2\n"); | 206 | fprintf(stderr, "tox1 invited tox2\n"); |
199 | } | 207 | } |
200 | 208 | ||
diff --git a/auto_tests/conference_test.c b/auto_tests/conference_test.c index 2bdee78f..fd4f41b4 100644 --- a/auto_tests/conference_test.c +++ b/auto_tests/conference_test.c | |||
@@ -15,30 +15,39 @@ | |||
15 | #include "../toxcore/util.h" | 15 | #include "../toxcore/util.h" |
16 | #include "check_compat.h" | 16 | #include "check_compat.h" |
17 | 17 | ||
18 | #define NUM_GROUP_TOX 5 | 18 | #define NUM_GROUP_TOX 16 |
19 | #define GROUP_MESSAGE "Install Gentoo" | 19 | #define GROUP_MESSAGE "Install Gentoo" |
20 | 20 | ||
21 | #define NAME_FORMAT_STR "Tox #%4u" | ||
22 | #define NAMELEN 9 | ||
23 | #define NAME_FORMAT "%9s" | ||
24 | |||
25 | typedef struct State { | ||
26 | uint32_t id; | ||
27 | bool invited_next; | ||
28 | } State; | ||
29 | |||
21 | static void handle_self_connection_status( | 30 | static void handle_self_connection_status( |
22 | Tox *tox, TOX_CONNECTION connection_status, void *user_data) | 31 | Tox *tox, TOX_CONNECTION connection_status, void *user_data) |
23 | { | 32 | { |
24 | const uint16_t id = *(uint16_t *)user_data; | 33 | const State *state = (State *)user_data; |
25 | 34 | ||
26 | if (connection_status != TOX_CONNECTION_NONE) { | 35 | if (connection_status != TOX_CONNECTION_NONE) { |
27 | printf("tox #%d: is now connected\n", id); | 36 | printf("tox #%u: is now connected\n", state->id); |
28 | } else { | 37 | } else { |
29 | printf("tox #%d: is now disconnected\n", id); | 38 | printf("tox #%u: is now disconnected\n", state->id); |
30 | } | 39 | } |
31 | } | 40 | } |
32 | 41 | ||
33 | static void handle_friend_connection_status( | 42 | static void handle_friend_connection_status( |
34 | Tox *tox, uint32_t friendnumber, TOX_CONNECTION connection_status, void *user_data) | 43 | Tox *tox, uint32_t friendnumber, TOX_CONNECTION connection_status, void *user_data) |
35 | { | 44 | { |
36 | const uint16_t id = *(uint16_t *)user_data; | 45 | const State *state = (State *)user_data; |
37 | 46 | ||
38 | if (connection_status != TOX_CONNECTION_NONE) { | 47 | if (connection_status != TOX_CONNECTION_NONE) { |
39 | printf("tox #%d: is now connected to friend %d\n", id, friendnumber); | 48 | printf("tox #%u: is now connected to friend %u\n", state->id, friendnumber); |
40 | } else { | 49 | } else { |
41 | printf("tox #%d: is now disconnected from friend %d\n", id, friendnumber); | 50 | printf("tox #%u: is now disconnected from friend %u\n", state->id, friendnumber); |
42 | } | 51 | } |
43 | } | 52 | } |
44 | 53 | ||
@@ -46,26 +55,44 @@ static void handle_conference_invite( | |||
46 | Tox *tox, uint32_t friendnumber, TOX_CONFERENCE_TYPE type, | 55 | Tox *tox, uint32_t friendnumber, TOX_CONFERENCE_TYPE type, |
47 | const uint8_t *data, size_t length, void *user_data) | 56 | const uint8_t *data, size_t length, void *user_data) |
48 | { | 57 | { |
49 | const uint16_t id = *(uint16_t *)user_data; | 58 | const State *state = (State *)user_data; |
50 | ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%d: wrong conference type: %d", id, type); | 59 | ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %d", state->id, type); |
51 | 60 | ||
52 | TOX_ERR_CONFERENCE_JOIN err; | 61 | TOX_ERR_CONFERENCE_JOIN err; |
53 | uint32_t g_num = tox_conference_join(tox, friendnumber, data, length, &err); | 62 | uint32_t g_num = tox_conference_join(tox, friendnumber, data, length, &err); |
54 | 63 | ||
55 | ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%d: error joining group: %d", id, err); | 64 | ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%u: error joining group: %d", state->id, err); |
56 | ck_assert_msg(g_num == 0, "tox #%d: group number was not 0", id); | 65 | ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", state->id); |
57 | 66 | ||
58 | // Try joining again. We should only be allowed to join once. | 67 | // Try joining again. We should only be allowed to join once. |
59 | tox_conference_join(tox, friendnumber, data, length, &err); | 68 | tox_conference_join(tox, friendnumber, data, length, &err); |
60 | ck_assert_msg(err != TOX_ERR_CONFERENCE_JOIN_OK, | 69 | ck_assert_msg(err != TOX_ERR_CONFERENCE_JOIN_OK, |
61 | "tox #%d: joining groupchat twice should be impossible.", id); | 70 | "tox #%u: joining groupchat twice should be impossible.", state->id); |
71 | } | ||
62 | 72 | ||
63 | if (tox_self_get_friend_list_size(tox) > 1) { | 73 | static void handle_conference_peer_list_changed( |
64 | printf("tox #%d: inviting next friend\n", id); | 74 | Tox *tox, uint32_t conference_number, void *user_data) |
65 | ck_assert_msg(tox_conference_invite(tox, 1, g_num, nullptr) != 0, "failed to invite friend"); | 75 | { |
66 | } else { | 76 | State *state = (State *)user_data; |
67 | printf("tox #%d was the last tox, no further invites happening\n", id); | 77 | |
78 | if (state->invited_next || tox_self_get_friend_list_size(tox) <= 1) { | ||
79 | return; | ||
68 | } | 80 | } |
81 | |||
82 | // TODO(zugz): neater way to determine whether we are connected, and when | ||
83 | // we become so | ||
84 | TOX_ERR_CONFERENCE_PEER_QUERY peer_err; | ||
85 | tox_conference_peer_number_is_ours(tox, 0, 0, &peer_err); | ||
86 | |||
87 | if (peer_err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { | ||
88 | return; | ||
89 | } | ||
90 | |||
91 | TOX_ERR_CONFERENCE_INVITE err; | ||
92 | tox_conference_invite(tox, 1, 0, &err); | ||
93 | ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %d", state->id, err); | ||
94 | printf("tox #%u: invited next friend\n", state->id); | ||
95 | state->invited_next = true; | ||
69 | } | 96 | } |
70 | 97 | ||
71 | static uint16_t num_recv; | 98 | static uint16_t num_recv; |
@@ -79,7 +106,7 @@ static void handle_conference_message( | |||
79 | } | 106 | } |
80 | } | 107 | } |
81 | 108 | ||
82 | static void run_conference_tests(Tox **toxes, uint32_t *tox_index) | 109 | static void run_conference_tests(Tox **toxes, State *state) |
83 | { | 110 | { |
84 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | 111 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
85 | tox_callback_conference_message(toxes[i], &handle_conference_message); | 112 | tox_callback_conference_message(toxes[i], &handle_conference_message); |
@@ -96,7 +123,7 @@ static void run_conference_tests(Tox **toxes, uint32_t *tox_index) | |||
96 | 123 | ||
97 | for (uint8_t j = 0; j < 20; ++j) { | 124 | for (uint8_t j = 0; j < 20; ++j) { |
98 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | 125 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
99 | tox_iterate(toxes[i], &tox_index[i]); | 126 | tox_iterate(toxes[i], &state[i]); |
100 | } | 127 | } |
101 | 128 | ||
102 | c_sleep(25); | 129 | c_sleep(25); |
@@ -105,12 +132,27 @@ static void run_conference_tests(Tox **toxes, uint32_t *tox_index) | |||
105 | c_sleep(25); | 132 | c_sleep(25); |
106 | ck_assert_msg(num_recv == NUM_GROUP_TOX, "failed to recv group messages"); | 133 | ck_assert_msg(num_recv == NUM_GROUP_TOX, "failed to recv group messages"); |
107 | 134 | ||
135 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | ||
136 | for (uint16_t j = 0; j < NUM_GROUP_TOX; ++j) { | ||
137 | const size_t len = tox_conference_peer_get_name_size(toxes[i], 0, j, nullptr); | ||
138 | ck_assert_msg(len == NAMELEN, "name of #%u according to #%u has incorrect length %u", state[j].id, state[i].id, | ||
139 | (unsigned int)len); | ||
140 | uint8_t name[NAMELEN]; | ||
141 | tox_conference_peer_get_name(toxes[i], 0, j, name, nullptr); | ||
142 | char expected_name[NAMELEN + 1]; | ||
143 | snprintf(expected_name, NAMELEN + 1, NAME_FORMAT_STR, state[j].id); | ||
144 | ck_assert_msg(memcmp(name, expected_name, NAMELEN) == 0, | ||
145 | "name of #%u according to #%u is \"" NAME_FORMAT "\"; expected \"%s\"", | ||
146 | state[j].id, state[i].id, name, expected_name); | ||
147 | } | ||
148 | } | ||
149 | |||
108 | for (uint16_t k = NUM_GROUP_TOX; k != 0 ; --k) { | 150 | for (uint16_t k = NUM_GROUP_TOX; k != 0 ; --k) { |
109 | tox_conference_delete(toxes[k - 1], 0, nullptr); | 151 | tox_conference_delete(toxes[k - 1], 0, nullptr); |
110 | 152 | ||
111 | for (uint8_t j = 0; j < 10; ++j) { | 153 | for (uint8_t j = 0; j < 10; ++j) { |
112 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | 154 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
113 | tox_iterate(toxes[i], &tox_index[i]); | 155 | tox_iterate(toxes[i], &state[i]); |
114 | } | 156 | } |
115 | 157 | ||
116 | c_sleep(50); | 158 | c_sleep(50); |
@@ -130,7 +172,8 @@ static void test_many_group(void) | |||
130 | const time_t test_start_time = time(nullptr); | 172 | const time_t test_start_time = time(nullptr); |
131 | 173 | ||
132 | Tox *toxes[NUM_GROUP_TOX]; | 174 | Tox *toxes[NUM_GROUP_TOX]; |
133 | uint32_t tox_index[NUM_GROUP_TOX]; | 175 | State state[NUM_GROUP_TOX]; |
176 | memset(state, 0, NUM_GROUP_TOX * sizeof(State)); | ||
134 | time_t cur_time = time(nullptr); | 177 | time_t cur_time = time(nullptr); |
135 | struct Tox_Options *opts = tox_options_new(nullptr); | 178 | struct Tox_Options *opts = tox_options_new(nullptr); |
136 | tox_options_set_start_port(opts, 33445); | 179 | tox_options_set_start_port(opts, 33445); |
@@ -140,13 +183,18 @@ static void test_many_group(void) | |||
140 | 183 | ||
141 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | 184 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
142 | TOX_ERR_NEW err; | 185 | TOX_ERR_NEW err; |
143 | tox_index[i] = i + 1; | 186 | state[i].id = i + 1; |
144 | toxes[i] = tox_new_log(opts, &err, &tox_index[i]); | 187 | toxes[i] = tox_new_log(opts, &err, &state[i]); |
145 | 188 | ||
146 | ck_assert_msg(toxes[i] != nullptr, "failed to create tox instance %u: error %d", i, err); | 189 | ck_assert_msg(toxes[i] != nullptr, "failed to create tox instance %u: error %d", i, err); |
147 | tox_callback_self_connection_status(toxes[i], &handle_self_connection_status); | 190 | tox_callback_self_connection_status(toxes[i], &handle_self_connection_status); |
148 | tox_callback_friend_connection_status(toxes[i], &handle_friend_connection_status); | 191 | tox_callback_friend_connection_status(toxes[i], &handle_friend_connection_status); |
149 | tox_callback_conference_invite(toxes[i], &handle_conference_invite); | 192 | tox_callback_conference_invite(toxes[i], &handle_conference_invite); |
193 | tox_callback_conference_peer_list_changed(toxes[i], &handle_conference_peer_list_changed); | ||
194 | |||
195 | char name[NAMELEN + 1]; | ||
196 | snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, state[i].id); | ||
197 | tox_self_set_name(toxes[i], (const uint8_t *)name, NAMELEN, nullptr); | ||
150 | 198 | ||
151 | if (i != 0) { | 199 | if (i != 0) { |
152 | uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; | 200 | uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; |
@@ -181,11 +229,11 @@ static void test_many_group(void) | |||
181 | online_count = 0; | 229 | online_count = 0; |
182 | 230 | ||
183 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | 231 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
184 | tox_iterate(toxes[i], &tox_index[i]); | 232 | tox_iterate(toxes[i], &state[i]); |
185 | online_count += tox_friend_get_connection_status(toxes[i], 0, nullptr) != TOX_CONNECTION_NONE; | 233 | online_count += tox_friend_get_connection_status(toxes[i], 0, nullptr) != TOX_CONNECTION_NONE; |
186 | } | 234 | } |
187 | 235 | ||
188 | printf("currently %d toxes are online\n", online_count); | 236 | printf("currently %u toxes are online\n", online_count); |
189 | fflush(stdout); | 237 | fflush(stdout); |
190 | 238 | ||
191 | c_sleep(1000); | 239 | c_sleep(1000); |
@@ -194,40 +242,57 @@ static void test_many_group(void) | |||
194 | printf("friends connected, took %d seconds\n", (int)(time(nullptr) - cur_time)); | 242 | printf("friends connected, took %d seconds\n", (int)(time(nullptr) - cur_time)); |
195 | 243 | ||
196 | ck_assert_msg(tox_conference_new(toxes[0], nullptr) != UINT32_MAX, "failed to create group"); | 244 | ck_assert_msg(tox_conference_new(toxes[0], nullptr) != UINT32_MAX, "failed to create group"); |
197 | printf("tox #%d: inviting its first friend\n", tox_index[0]); | 245 | printf("tox #%u: inviting its first friend\n", state[0].id); |
198 | ck_assert_msg(tox_conference_invite(toxes[0], 0, 0, nullptr) != 0, "failed to invite friend"); | 246 | ck_assert_msg(tox_conference_invite(toxes[0], 0, 0, nullptr) != 0, "failed to invite friend"); |
247 | state[0].invited_next = true; | ||
199 | ck_assert_msg(tox_conference_set_title(toxes[0], 0, (const uint8_t *)"Gentoo", sizeof("Gentoo") - 1, nullptr) != 0, | 248 | ck_assert_msg(tox_conference_set_title(toxes[0], 0, (const uint8_t *)"Gentoo", sizeof("Gentoo") - 1, nullptr) != 0, |
200 | "failed to set group title"); | 249 | "failed to set group title"); |
201 | 250 | ||
202 | // One iteration for all the invitations to happen. | 251 | |
203 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | 252 | printf("waiting for invitations to be made\n"); |
204 | tox_iterate(toxes[i], &tox_index[i]); | 253 | uint16_t invited_count = 0; |
254 | |||
255 | while (invited_count != NUM_GROUP_TOX - 1) { | ||
256 | invited_count = 0; | ||
257 | |||
258 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | ||
259 | tox_iterate(toxes[i], &state[i]); | ||
260 | invited_count += state[i].invited_next; | ||
261 | } | ||
262 | |||
263 | c_sleep(50); | ||
205 | } | 264 | } |
206 | 265 | ||
207 | cur_time = time(nullptr); | 266 | cur_time = time(nullptr); |
208 | printf("waiting for all toxes to be in the group\n"); | 267 | printf("waiting for all toxes to be in the group\n"); |
209 | unsigned invited_count = 0; | 268 | uint16_t fully_connected_count = 0; |
210 | 269 | ||
211 | while (invited_count != NUM_GROUP_TOX) { | 270 | while (fully_connected_count != NUM_GROUP_TOX) { |
212 | invited_count = 0; | 271 | fully_connected_count = 0; |
213 | printf("current peer counts: ["); | 272 | printf("current peer counts: ["); |
214 | 273 | ||
215 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | 274 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
216 | tox_iterate(toxes[i], &tox_index[i]); | 275 | tox_iterate(toxes[i], &state[i]); |
217 | uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, nullptr); | 276 | TOX_ERR_CONFERENCE_PEER_QUERY err; |
218 | invited_count += peer_count == NUM_GROUP_TOX; | 277 | uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, &err); |
278 | |||
279 | if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { | ||
280 | peer_count = 0; | ||
281 | } | ||
282 | |||
283 | fully_connected_count += peer_count == NUM_GROUP_TOX; | ||
219 | 284 | ||
220 | if (i != 0) { | 285 | if (i != 0) { |
221 | printf(", "); | 286 | printf(", "); |
222 | } | 287 | } |
223 | 288 | ||
224 | printf("%d", peer_count); | 289 | printf("%u", peer_count); |
225 | } | 290 | } |
226 | 291 | ||
227 | printf("]\n"); | 292 | printf("]\n"); |
228 | fflush(stdout); | 293 | fflush(stdout); |
229 | 294 | ||
230 | c_sleep(1000); | 295 | c_sleep(200); |
231 | } | 296 | } |
232 | 297 | ||
233 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { | 298 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
@@ -246,7 +311,7 @@ static void test_many_group(void) | |||
246 | 311 | ||
247 | printf("group connected, took %d seconds\n", (int)(time(nullptr) - cur_time)); | 312 | printf("group connected, took %d seconds\n", (int)(time(nullptr) - cur_time)); |
248 | 313 | ||
249 | run_conference_tests(toxes, tox_index); | 314 | run_conference_tests(toxes, state); |
250 | 315 | ||
251 | printf("tearing down toxes\n"); | 316 | printf("tearing down toxes\n"); |
252 | 317 | ||
diff --git a/toxcore/group.c b/toxcore/group.c index 826ea239..317b885f 100644 --- a/toxcore/group.c +++ b/toxcore/group.c | |||
@@ -405,8 +405,12 @@ static int connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *user | |||
405 | get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[i].number); | 405 | get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[i].number); |
406 | 406 | ||
407 | if (!pk_in_closest_peers(g, real_pk)) { | 407 | if (!pk_in_closest_peers(g, real_pk)) { |
408 | g->close[i].type = GROUPCHAT_CLOSE_NONE; | 408 | g->close[i].closest = false; |
409 | kill_friend_connection(g_c->fr_c, g->close[i].number); | 409 | |
410 | if (!g->close[i].introducer && !g->close[i].introduced) { | ||
411 | g->close[i].type = GROUPCHAT_CLOSE_NONE; | ||
412 | kill_friend_connection(g_c->fr_c, g->close[i].number); | ||
413 | } | ||
410 | } | 414 | } |
411 | } | 415 | } |
412 | 416 | ||
@@ -618,6 +622,8 @@ static int setnick(Group_Chats *g_c, uint32_t groupnumber, int peer_index, const | |||
618 | return -1; | 622 | return -1; |
619 | } | 623 | } |
620 | 624 | ||
625 | g->group[peer_index].nick_updated = true; | ||
626 | |||
621 | /* same name as already stored? */ | 627 | /* same name as already stored? */ |
622 | if (g->group[peer_index].nick_len == nick_len) { | 628 | if (g->group[peer_index].nick_len == nick_len) { |
623 | if (nick_len == 0 || !memcmp(g->group[peer_index].nick, nick, nick_len)) { | 629 | if (nick_len == 0 || !memcmp(g->group[peer_index].nick, nick, nick_len)) { |
@@ -740,7 +746,7 @@ static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t gr | |||
740 | } | 746 | } |
741 | 747 | ||
742 | if (g->close[i].number == (uint32_t)friendcon_id) { | 748 | if (g->close[i].number == (uint32_t)friendcon_id) { |
743 | g->close[i].closest = closest; | 749 | g->close[i].closest |= closest; |
744 | return i; /* Already in list. */ | 750 | return i; /* Already in list. */ |
745 | } | 751 | } |
746 | } | 752 | } |
@@ -756,6 +762,8 @@ static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, uint32_t gr | |||
756 | g->close[ind].type = GROUPCHAT_CLOSE_CONNECTION; | 762 | g->close[ind].type = GROUPCHAT_CLOSE_CONNECTION; |
757 | g->close[ind].number = friendcon_id; | 763 | g->close[ind].number = friendcon_id; |
758 | g->close[ind].closest = closest; | 764 | g->close[ind].closest = closest; |
765 | g->close[ind].introducer = false; | ||
766 | g->close[ind].introduced = false; | ||
759 | // TODO(irungentoo): | 767 | // TODO(irungentoo): |
760 | friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &g_handle_status, &g_handle_packet, | 768 | friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &g_handle_status, &g_handle_packet, |
761 | &handle_lossy, g_c, friendcon_id); | 769 | &handle_lossy, g_c, friendcon_id); |
@@ -1062,6 +1070,7 @@ static unsigned int send_lossy_group_peer(Friend_Connections *fr_c, int friendco | |||
1062 | * return 0 on success. | 1070 | * return 0 on success. |
1063 | * return -1 if groupnumber is invalid. | 1071 | * return -1 if groupnumber is invalid. |
1064 | * return -2 if invite packet failed to send. | 1072 | * return -2 if invite packet failed to send. |
1073 | * return -3 if we are not connected to the group chat. | ||
1065 | */ | 1074 | */ |
1066 | int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) | 1075 | int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) |
1067 | { | 1076 | { |
@@ -1071,6 +1080,10 @@ int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) | |||
1071 | return -1; | 1080 | return -1; |
1072 | } | 1081 | } |
1073 | 1082 | ||
1083 | if (g->status != GROUPCHAT_STATUS_CONNECTED) { | ||
1084 | return -3; | ||
1085 | } | ||
1086 | |||
1074 | uint8_t invite[INVITE_PACKET_SIZE]; | 1087 | uint8_t invite[INVITE_PACKET_SIZE]; |
1075 | invite[0] = INVITE_ID; | 1088 | invite[0] = INVITE_ID; |
1076 | uint16_t groupchat_num = net_htons((uint16_t)groupnumber); | 1089 | uint16_t groupchat_num = net_htons((uint16_t)groupnumber); |
@@ -1081,7 +1094,6 @@ int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) | |||
1081 | return 0; | 1094 | return 0; |
1082 | } | 1095 | } |
1083 | 1096 | ||
1084 | wipe_group_chat(g_c, groupnumber); | ||
1085 | return -2; | 1097 | return -2; |
1086 | } | 1098 | } |
1087 | 1099 | ||
@@ -1148,6 +1160,7 @@ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_typ | |||
1148 | g->close[close_index].group_number = other_groupnum; | 1160 | g->close[close_index].group_number = other_groupnum; |
1149 | g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; | 1161 | g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; |
1150 | g->number_joined = friendcon_id; | 1162 | g->number_joined = friendcon_id; |
1163 | g->close[close_index].introducer = true; | ||
1151 | } | 1164 | } |
1152 | 1165 | ||
1153 | send_peer_query(g_c, friendcon_id, other_groupnum); | 1166 | send_peer_query(g_c, friendcon_id, other_groupnum); |
@@ -1503,6 +1516,7 @@ static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, con | |||
1503 | if (close_index != -1) { | 1516 | if (close_index != -1) { |
1504 | g->close[close_index].group_number = other_groupnum; | 1517 | g->close[close_index].group_number = other_groupnum; |
1505 | g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; | 1518 | g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; |
1519 | g->close[close_index].introduced = true; | ||
1506 | } | 1520 | } |
1507 | 1521 | ||
1508 | group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); | 1522 | group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); |
@@ -1606,21 +1620,6 @@ static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_ | |||
1606 | g->close[index].type = GROUPCHAT_CLOSE_ONLINE; | 1620 | g->close[index].type = GROUPCHAT_CLOSE_ONLINE; |
1607 | send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); | 1621 | send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); |
1608 | 1622 | ||
1609 | if (g->number_joined != -1 && count_close_connected(g) >= DESIRED_CLOSE_CONNECTIONS) { | ||
1610 | int fr_close_index = friend_in_close(g, g->number_joined); | ||
1611 | |||
1612 | if (fr_close_index == -1) { | ||
1613 | return -1; | ||
1614 | } | ||
1615 | |||
1616 | if (!g->close[fr_close_index].closest) { | ||
1617 | g->close[fr_close_index].type = GROUPCHAT_CLOSE_NONE; | ||
1618 | send_peer_kill(g_c, g->close[fr_close_index].number, g->close[fr_close_index].group_number); | ||
1619 | kill_friend_connection(g_c->fr_c, g->close[fr_close_index].number); | ||
1620 | g->number_joined = -1; | ||
1621 | } | ||
1622 | } | ||
1623 | |||
1624 | return 0; | 1623 | return 0; |
1625 | } | 1624 | } |
1626 | 1625 | ||
@@ -1729,11 +1728,6 @@ static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8 | |||
1729 | memcpy(&peer_num, d, sizeof(peer_num)); | 1728 | memcpy(&peer_num, d, sizeof(peer_num)); |
1730 | peer_num = net_ntohs(peer_num); | 1729 | peer_num = net_ntohs(peer_num); |
1731 | d += sizeof(uint16_t); | 1730 | d += sizeof(uint16_t); |
1732 | int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, true); | ||
1733 | |||
1734 | if (peer_index == -1) { | ||
1735 | return -1; | ||
1736 | } | ||
1737 | 1731 | ||
1738 | if (g->status == GROUPCHAT_STATUS_VALID | 1732 | if (g->status == GROUPCHAT_STATUS_VALID |
1739 | && public_key_cmp(d, nc_get_self_public_key(g_c->m->net_crypto)) == 0) { | 1733 | && public_key_cmp(d, nc_get_self_public_key(g_c->m->net_crypto)) == 0) { |
@@ -1742,6 +1736,12 @@ static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8 | |||
1742 | group_name_send(g_c, groupnumber, g_c->m->name, g_c->m->name_length); | 1736 | group_name_send(g_c, groupnumber, g_c->m->name, g_c->m->name_length); |
1743 | } | 1737 | } |
1744 | 1738 | ||
1739 | int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, true); | ||
1740 | |||
1741 | if (peer_index == -1) { | ||
1742 | return -1; | ||
1743 | } | ||
1744 | |||
1745 | d += CRYPTO_PUBLIC_KEY_SIZE * 2; | 1745 | d += CRYPTO_PUBLIC_KEY_SIZE * 2; |
1746 | uint8_t name_length = *d; | 1746 | uint8_t name_length = *d; |
1747 | d += 1; | 1747 | d += 1; |
@@ -1750,7 +1750,10 @@ static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8 | |||
1750 | return -1; | 1750 | return -1; |
1751 | } | 1751 | } |
1752 | 1752 | ||
1753 | setnick(g_c, groupnumber, peer_index, d, name_length, userdata, true); | 1753 | if (!g->group[peer_index].nick_updated) { |
1754 | setnick(g_c, groupnumber, peer_index, d, name_length, userdata, true); | ||
1755 | } | ||
1756 | |||
1754 | d += name_length; | 1757 | d += name_length; |
1755 | } | 1758 | } |
1756 | 1759 | ||
@@ -2037,6 +2040,58 @@ int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const | |||
2037 | return 0; | 2040 | return 0; |
2038 | } | 2041 | } |
2039 | 2042 | ||
2043 | static Message_Info *find_message_slot_or_reject(uint32_t message_number, uint8_t message_id, Group_Peer *peer) | ||
2044 | { | ||
2045 | const bool ignore_older = (message_id == GROUP_MESSAGE_NAME_ID || message_id == GROUP_MESSAGE_TITLE_ID); | ||
2046 | |||
2047 | Message_Info *i; | ||
2048 | |||
2049 | for (i = peer->last_message_infos; i < peer->last_message_infos + peer->num_last_message_infos; ++i) { | ||
2050 | if (message_number > i->message_number) { | ||
2051 | break; | ||
2052 | } | ||
2053 | |||
2054 | if (message_number == i->message_number) { | ||
2055 | return nullptr; | ||
2056 | } | ||
2057 | |||
2058 | if (ignore_older && message_id == i->message_id) { | ||
2059 | return nullptr; | ||
2060 | } | ||
2061 | } | ||
2062 | |||
2063 | return i; | ||
2064 | } | ||
2065 | |||
2066 | /* Stores message info in peer->last_message_infos. | ||
2067 | * | ||
2068 | * return true if message should be processed; | ||
2069 | * return false otherwise. | ||
2070 | */ | ||
2071 | static bool check_message_info(uint32_t message_number, uint8_t message_id, Group_Peer *peer) | ||
2072 | { | ||
2073 | Message_Info *const i = find_message_slot_or_reject(message_number, message_id, peer); | ||
2074 | |||
2075 | if (i == nullptr) { | ||
2076 | return false; | ||
2077 | } | ||
2078 | |||
2079 | if (i == peer->last_message_infos + MAX_LAST_MESSAGE_INFOS) { | ||
2080 | return false; | ||
2081 | } | ||
2082 | |||
2083 | if (peer->num_last_message_infos < MAX_LAST_MESSAGE_INFOS) { | ||
2084 | ++peer->num_last_message_infos; | ||
2085 | } | ||
2086 | |||
2087 | memmove(i + 1, i, ((peer->last_message_infos + peer->num_last_message_infos - 1) - i) * sizeof(Message_Info)); | ||
2088 | |||
2089 | i->message_number = message_number; | ||
2090 | i->message_id = message_id; | ||
2091 | |||
2092 | return true; | ||
2093 | } | ||
2094 | |||
2040 | static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, | 2095 | static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, |
2041 | int close_index, void *userdata) | 2096 | int close_index, void *userdata) |
2042 | { | 2097 | { |
@@ -2063,23 +2118,41 @@ static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, | |||
2063 | return; | 2118 | return; |
2064 | } | 2119 | } |
2065 | 2120 | ||
2121 | if (g->number_joined != -1 && count_close_connected(g) >= DESIRED_CLOSE_CONNECTIONS) { | ||
2122 | const int fr_close_index = friend_in_close(g, g->number_joined); | ||
2123 | |||
2124 | if (fr_close_index >= 0 && fr_close_index != close_index && !g->close[fr_close_index].closest) { | ||
2125 | uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; | ||
2126 | get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->close[fr_close_index].number); | ||
2127 | |||
2128 | if (id_equal(g->group[index].real_pk, real_pk)) { | ||
2129 | /* Received message from peer relayed via another peer, so | ||
2130 | * the introduction was successful */ | ||
2131 | g->number_joined = -1; | ||
2132 | g->close[fr_close_index].introducer = false; | ||
2133 | |||
2134 | if (!g->close[fr_close_index].closest && !g->close[fr_close_index].introduced) { | ||
2135 | g->close[fr_close_index].type = GROUPCHAT_CLOSE_NONE; | ||
2136 | send_peer_kill(g_c, g->close[fr_close_index].number, g->close[fr_close_index].group_number); | ||
2137 | kill_friend_connection(g_c->fr_c, g->close[fr_close_index].number); | ||
2138 | } | ||
2139 | } | ||
2140 | } | ||
2141 | } | ||
2142 | |||
2066 | uint32_t message_number; | 2143 | uint32_t message_number; |
2067 | memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); | 2144 | memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); |
2068 | message_number = net_ntohl(message_number); | 2145 | message_number = net_ntohl(message_number); |
2069 | 2146 | ||
2070 | if (g->group[index].last_message_number == 0) { | ||
2071 | g->group[index].last_message_number = message_number; | ||
2072 | } else if (message_number - g->group[index].last_message_number > 64 || | ||
2073 | message_number == g->group[index].last_message_number) { | ||
2074 | return; | ||
2075 | } | ||
2076 | |||
2077 | g->group[index].last_message_number = message_number; | ||
2078 | |||
2079 | uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)]; | 2147 | uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)]; |
2080 | const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(message_number) + 1; | 2148 | const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(message_number) + 1; |
2081 | uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1); | 2149 | uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1); |
2082 | 2150 | ||
2151 | // FIXME(zugz) update discussion of message numbers in the spec | ||
2152 | if (!check_message_info(message_number, message_id, &g->group[index])) { | ||
2153 | return; | ||
2154 | } | ||
2155 | |||
2083 | switch (message_id) { | 2156 | switch (message_id) { |
2084 | case GROUP_MESSAGE_PING_ID: { | 2157 | case GROUP_MESSAGE_PING_ID: { |
2085 | if (msg_data_len != 0) { | 2158 | if (msg_data_len != 0) { |
diff --git a/toxcore/group.h b/toxcore/group.h index 9b4541c4..970cf7fb 100644 --- a/toxcore/group.h +++ b/toxcore/group.h | |||
@@ -39,15 +39,26 @@ typedef enum Groupchat_Type { | |||
39 | 39 | ||
40 | #define MAX_LOSSY_COUNT 256 | 40 | #define MAX_LOSSY_COUNT 256 |
41 | 41 | ||
42 | typedef struct Message_Info { | ||
43 | uint32_t message_number; | ||
44 | uint8_t message_id; | ||
45 | } Message_Info; | ||
46 | |||
47 | #define MAX_LAST_MESSAGE_INFOS 8 | ||
48 | |||
42 | typedef struct Group_Peer { | 49 | typedef struct Group_Peer { |
43 | uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; | 50 | uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; |
44 | uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; | 51 | uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; |
45 | 52 | ||
46 | uint64_t last_recv; | 53 | uint64_t last_recv; |
47 | uint32_t last_message_number; | 54 | |
55 | Message_Info | ||
56 | last_message_infos[MAX_LAST_MESSAGE_INFOS]; /* received messages, strictly decreasing in message_number */ | ||
57 | uint8_t num_last_message_infos; | ||
48 | 58 | ||
49 | uint8_t nick[MAX_NAME_LENGTH]; | 59 | uint8_t nick[MAX_NAME_LENGTH]; |
50 | uint8_t nick_len; | 60 | uint8_t nick_len; |
61 | bool nick_updated; | ||
51 | 62 | ||
52 | uint16_t peer_number; | 63 | uint16_t peer_number; |
53 | 64 | ||
@@ -70,7 +81,9 @@ typedef enum Groupchat_Close_Type { | |||
70 | 81 | ||
71 | typedef struct Groupchat_Close { | 82 | typedef struct Groupchat_Close { |
72 | uint8_t type; /* GROUPCHAT_CLOSE_* */ | 83 | uint8_t type; /* GROUPCHAT_CLOSE_* */ |
73 | uint8_t closest; | 84 | bool closest; /* connected to peer because it is one of our closest peers */ |
85 | bool introducer; /* connected to peer because it introduced us to the group */ | ||
86 | bool introduced; /* connected to peer because we introduced it to the group */ | ||
74 | uint32_t number; | 87 | uint32_t number; |
75 | uint16_t group_number; | 88 | uint16_t group_number; |
76 | } Groupchat_Close; | 89 | } Groupchat_Close; |
@@ -91,6 +104,7 @@ typedef struct Group_c { | |||
91 | Group_Peer *group; | 104 | Group_Peer *group; |
92 | uint32_t numpeers; | 105 | uint32_t numpeers; |
93 | 106 | ||
107 | /* TODO(zugz) rename close to something more accurate - "connected"? */ | ||
94 | Groupchat_Close close[MAX_GROUP_CONNECTIONS]; | 108 | Groupchat_Close close[MAX_GROUP_CONNECTIONS]; |
95 | 109 | ||
96 | uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; | 110 | uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; |
diff --git a/toxcore/tox.api.h b/toxcore/tox.api.h index 6cb3145b..7cc58e3b 100644 --- a/toxcore/tox.api.h +++ b/toxcore/tox.api.h | |||
@@ -2306,6 +2306,10 @@ namespace conference { | |||
2306 | * The invite packet failed to send. | 2306 | * The invite packet failed to send. |
2307 | */ | 2307 | */ |
2308 | FAIL_SEND, | 2308 | FAIL_SEND, |
2309 | /** | ||
2310 | * The client is not connected to the conference. | ||
2311 | */ | ||
2312 | NO_CONNECTION, | ||
2309 | } | 2313 | } |
2310 | 2314 | ||
2311 | 2315 | ||
diff --git a/toxcore/tox.c b/toxcore/tox.c index 101494cc..750a52fa 100644 --- a/toxcore/tox.c +++ b/toxcore/tox.c | |||
@@ -1290,6 +1290,10 @@ bool tox_conference_invite(Tox *tox, uint32_t friend_number, uint32_t conference | |||
1290 | case -2: | 1290 | case -2: |
1291 | SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_FAIL_SEND); | 1291 | SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_FAIL_SEND); |
1292 | return false; | 1292 | return false; |
1293 | |||
1294 | case -3: | ||
1295 | SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_NO_CONNECTION); | ||
1296 | return false; | ||
1293 | } | 1297 | } |
1294 | 1298 | ||
1295 | SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_OK); | 1299 | SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_OK); |
diff --git a/toxcore/tox.h b/toxcore/tox.h index 4c83ca23..35ff19e7 100644 --- a/toxcore/tox.h +++ b/toxcore/tox.h | |||
@@ -2613,6 +2613,11 @@ typedef enum TOX_ERR_CONFERENCE_INVITE { | |||
2613 | */ | 2613 | */ |
2614 | TOX_ERR_CONFERENCE_INVITE_FAIL_SEND, | 2614 | TOX_ERR_CONFERENCE_INVITE_FAIL_SEND, |
2615 | 2615 | ||
2616 | /** | ||
2617 | * The client is not connected to the conference. | ||
2618 | */ | ||
2619 | TOX_ERR_CONFERENCE_INVITE_NO_CONNECTION, | ||
2620 | |||
2616 | } TOX_ERR_CONFERENCE_INVITE; | 2621 | } TOX_ERR_CONFERENCE_INVITE; |
2617 | 2622 | ||
2618 | 2623 | ||