/work/toxcore/group_connection.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* SPDX-License-Identifier: GPL-3.0-or-later |
2 | | * Copyright © 2016-2020 The TokTok team. |
3 | | * Copyright © 2015 Tox project. |
4 | | */ |
5 | | |
6 | | /** |
7 | | * An implementation of massive text only group chats. |
8 | | */ |
9 | | |
10 | | #include "group_connection.h" |
11 | | |
12 | | #include <assert.h> |
13 | | #include <stdint.h> |
14 | | #include <stdlib.h> |
15 | | #include <string.h> |
16 | | |
17 | | #include "DHT.h" |
18 | | #include "TCP_connection.h" |
19 | | #include "ccompat.h" |
20 | | #include "crypto_core.h" |
21 | | #include "group_chats.h" |
22 | | #include "group_common.h" |
23 | | #include "logger.h" |
24 | | #include "mono_time.h" |
25 | | #include "network.h" |
26 | | #include "util.h" |
27 | | |
28 | | /** Seconds since last direct UDP packet was received before the connection is considered dead */ |
29 | 53.4k | #define GCC_UDP_DIRECT_TIMEOUT (GC_PING_TIMEOUT + 4) |
30 | | |
31 | | /** Returns true if array entry does not contain an active packet. */ |
32 | | non_null() |
33 | | static bool array_entry_is_empty(const GC_Message_Array_Entry *array_entry) |
34 | 127k | { |
35 | 127k | assert(array_entry != nullptr); |
36 | 127k | return array_entry->time_added == 0; |
37 | 127k | } |
38 | | |
39 | | /** @brief Clears an array entry. */ |
40 | | non_null() |
41 | | static void clear_array_entry(GC_Message_Array_Entry *const array_entry) |
42 | 13.4k | { |
43 | 13.4k | if (array_entry->data != nullptr) { |
44 | 12.3k | free(array_entry->data); |
45 | 12.3k | } |
46 | | |
47 | 13.4k | *array_entry = (GC_Message_Array_Entry) { |
48 | 13.4k | nullptr |
49 | 13.4k | }; |
50 | 13.4k | } |
51 | | |
52 | | /** |
53 | | * Clears every send array message from queue starting at the index designated by |
54 | | * `start_id` and ending at `end_id`, and sets the send_message_id for `gconn` |
55 | | * to `start_id`. |
56 | | */ |
57 | | non_null() |
58 | | static void clear_send_queue_id_range(GC_Connection *gconn, uint64_t start_id, uint64_t end_id) |
59 | 0 | { |
60 | 0 | const uint16_t start_idx = gcc_get_array_index(start_id); |
61 | 0 | const uint16_t end_idx = gcc_get_array_index(end_id); |
62 | |
|
63 | 0 | for (uint16_t i = start_idx; i != end_idx; i = (i + 1) % GCC_BUFFER_SIZE) { |
64 | 0 | GC_Message_Array_Entry *entry = &gconn->send_array[i]; |
65 | 0 | clear_array_entry(entry); |
66 | 0 | } |
67 | |
|
68 | 0 | gconn->send_message_id = start_id; |
69 | 0 | } |
70 | | |
71 | | uint16_t gcc_get_array_index(uint64_t message_id) |
72 | 27.7k | { |
73 | 27.7k | return message_id % GCC_BUFFER_SIZE; |
74 | 27.7k | } |
75 | | |
76 | | void gcc_set_send_message_id(GC_Connection *gconn, uint64_t id) |
77 | 1.34k | { |
78 | 1.34k | gconn->send_message_id = id; |
79 | 1.34k | gconn->send_array_start = id % GCC_BUFFER_SIZE; |
80 | 1.34k | } |
81 | | |
82 | | void gcc_set_recv_message_id(GC_Connection *gconn, uint64_t id) |
83 | 13.7k | { |
84 | 13.7k | gconn->received_message_id = id; |
85 | 13.7k | } |
86 | | |
87 | | /** @brief Puts packet data in array_entry. |
88 | | * |
89 | | * Requires an empty array entry to be passed, and must not modify the passed |
90 | | * array entry on error. |
91 | | * |
92 | | * Return true on success. |
93 | | */ |
94 | | non_null(1, 2, 3) nullable(4) |
95 | | static bool create_array_entry(const Logger *log, const Mono_Time *mono_time, GC_Message_Array_Entry *array_entry, |
96 | | const uint8_t *data, uint16_t length, uint8_t packet_type, uint64_t message_id) |
97 | 14.1k | { |
98 | 14.1k | if (!array_entry_is_empty(array_entry)) { |
99 | 2 | LOGGER_WARNING(log, "Failed to create array entry; entry is not empty."); |
100 | 2 | return false; |
101 | 2 | } |
102 | | |
103 | 14.1k | if (length == 0) { |
104 | 1.18k | array_entry->data = nullptr; |
105 | 1.18k | array_entry->data_length = 0; |
106 | 12.9k | } else { |
107 | 12.9k | if (data == nullptr) { // should never happen |
108 | 0 | LOGGER_FATAL(log, "Got null data with non-zero length (length: %u, type %u)", |
109 | 0 | length, packet_type); |
110 | 0 | return false; |
111 | 0 | } |
112 | | |
113 | 12.9k | uint8_t *entry_data = (uint8_t *)malloc(length); |
114 | | |
115 | 12.9k | if (entry_data == nullptr) { |
116 | 11 | return false; |
117 | 11 | } |
118 | | |
119 | 12.9k | memcpy(entry_data, data, length); |
120 | 12.9k | array_entry->data = entry_data; |
121 | 12.9k | array_entry->data_length = length; |
122 | 12.9k | } |
123 | | |
124 | 14.1k | const uint64_t tm = mono_time_get(mono_time); |
125 | | |
126 | 14.1k | array_entry->packet_type = packet_type; |
127 | 14.1k | array_entry->message_id = message_id; |
128 | 14.1k | array_entry->time_added = tm; |
129 | 14.1k | array_entry->last_send_try = tm; |
130 | | |
131 | 14.1k | return true; |
132 | 14.1k | } |
133 | | |
134 | | /** @brief Adds data of length to gconn's send_array. |
135 | | * |
136 | | * Returns true and increments gconn's send_message_id on success. |
137 | | */ |
138 | | non_null(1, 2, 3) nullable(4) |
139 | | static bool add_to_send_array(const Logger *log, const Mono_Time *mono_time, GC_Connection *gconn, const uint8_t *data, |
140 | | uint16_t length, uint8_t packet_type) |
141 | 13.6k | { |
142 | | /* check if send_array is full */ |
143 | 13.6k | if ((gconn->send_message_id % GCC_BUFFER_SIZE) == (uint16_t)(gconn->send_array_start - 1)) { |
144 | 0 | LOGGER_DEBUG(log, "Send array overflow"); |
145 | 0 | return false; |
146 | 0 | } |
147 | | |
148 | 13.6k | const uint16_t idx = gcc_get_array_index(gconn->send_message_id); |
149 | 13.6k | GC_Message_Array_Entry *array_entry = &gconn->send_array[idx]; |
150 | | |
151 | 13.6k | if (!create_array_entry(log, mono_time, array_entry, data, length, packet_type, gconn->send_message_id)) { |
152 | 12 | return false; |
153 | 12 | } |
154 | | |
155 | 13.6k | ++gconn->send_message_id; |
156 | | |
157 | 13.6k | return true; |
158 | 13.6k | } |
159 | | |
160 | | int gcc_send_lossless_packet(const GC_Chat *chat, GC_Connection *gconn, const uint8_t *data, uint16_t length, |
161 | | uint8_t packet_type) |
162 | 13.0k | { |
163 | 13.0k | const uint64_t message_id = gconn->send_message_id; |
164 | | |
165 | 13.0k | if (!add_to_send_array(chat->log, chat->mono_time, gconn, data, length, packet_type)) { |
166 | 12 | LOGGER_WARNING(chat->log, "Failed to add payload to send array: (type: 0x%02x, length: %d)", packet_type, length); |
167 | 12 | return -1; |
168 | 12 | } |
169 | | |
170 | | // If the packet fails to wrap/encrypt, we remove it from the send array, since trying to-resend |
171 | | // the same bad packet probably won't help much. Otherwise we don't care if it doesn't successfully |
172 | | // send through the wire as it will keep retrying until the connection times out. |
173 | 13.0k | if (gcc_encrypt_and_send_lossless_packet(chat, gconn, data, length, message_id, packet_type) == -1) { |
174 | 39 | const uint16_t idx = gcc_get_array_index(message_id); |
175 | 39 | GC_Message_Array_Entry *array_entry = &gconn->send_array[idx]; |
176 | 39 | clear_array_entry(array_entry); |
177 | 39 | gconn->send_message_id = message_id; |
178 | 39 | LOGGER_ERROR(chat->log, "Failed to encrypt payload: (type: 0x%02x, length: %d)", packet_type, length); |
179 | 39 | return -2; |
180 | 39 | } |
181 | | |
182 | 13.0k | return 0; |
183 | 13.0k | } |
184 | | |
185 | | |
186 | | bool gcc_send_lossless_packet_fragments(const GC_Chat *chat, GC_Connection *gconn, const uint8_t *data, |
187 | | uint16_t length, uint8_t packet_type) |
188 | 185 | { |
189 | 185 | if (length <= MAX_GC_PACKET_CHUNK_SIZE || data == nullptr) { |
190 | 0 | LOGGER_FATAL(chat->log, "invalid length or null data pointer"); |
191 | 0 | return false; |
192 | 0 | } |
193 | | |
194 | 185 | const uint16_t start_id = gconn->send_message_id; |
195 | | |
196 | | // First packet segment is comprised of packet type + first chunk of payload |
197 | 185 | uint8_t chunk[MAX_GC_PACKET_CHUNK_SIZE]; |
198 | 185 | chunk[0] = packet_type; |
199 | 185 | memcpy(chunk + 1, data, MAX_GC_PACKET_CHUNK_SIZE - 1); |
200 | | |
201 | 185 | if (!add_to_send_array(chat->log, chat->mono_time, gconn, chunk, MAX_GC_PACKET_CHUNK_SIZE, GP_FRAGMENT)) { |
202 | 0 | return false; |
203 | 0 | } |
204 | | |
205 | 185 | uint16_t processed = MAX_GC_PACKET_CHUNK_SIZE - 1; |
206 | | |
207 | | // The rest of the segments are added in chunks |
208 | 448 | while (length > processed) { |
209 | 263 | const uint16_t chunk_len = min_u16(MAX_GC_PACKET_CHUNK_SIZE, length - processed); |
210 | | |
211 | 263 | memcpy(chunk, data + processed, chunk_len); |
212 | 263 | processed += chunk_len; |
213 | | |
214 | 263 | if (!add_to_send_array(chat->log, chat->mono_time, gconn, chunk, chunk_len, GP_FRAGMENT)) { |
215 | 0 | clear_send_queue_id_range(gconn, start_id, gconn->send_message_id); |
216 | 0 | return false; |
217 | 0 | } |
218 | 263 | } |
219 | | |
220 | | // empty packet signals the end of the sequence |
221 | 185 | if (!add_to_send_array(chat->log, chat->mono_time, gconn, nullptr, 0, GP_FRAGMENT)) { |
222 | 0 | clear_send_queue_id_range(gconn, start_id, gconn->send_message_id); |
223 | 0 | return false; |
224 | 0 | } |
225 | | |
226 | 185 | const uint16_t start_idx = gcc_get_array_index(start_id); |
227 | 185 | const uint16_t end_idx = gcc_get_array_index(gconn->send_message_id); |
228 | | |
229 | 818 | for (uint16_t i = start_idx; i != end_idx; i = (i + 1) % GCC_BUFFER_SIZE) { |
230 | 633 | const GC_Message_Array_Entry *entry = &gconn->send_array[i]; |
231 | | |
232 | 633 | if (array_entry_is_empty(entry)) { |
233 | 0 | LOGGER_FATAL(chat->log, "array entry for packet chunk is empty"); |
234 | 0 | return false; |
235 | 0 | } |
236 | | |
237 | 633 | assert(entry->packet_type == GP_FRAGMENT); |
238 | | |
239 | 633 | gcc_encrypt_and_send_lossless_packet(chat, gconn, entry->data, entry->data_length, |
240 | 633 | entry->message_id, entry->packet_type); |
241 | 633 | } |
242 | | |
243 | 185 | return true; |
244 | 185 | } |
245 | | |
246 | | bool gcc_handle_ack(const Logger *log, GC_Connection *gconn, uint64_t message_id) |
247 | 13.0k | { |
248 | 13.0k | uint16_t idx = gcc_get_array_index(message_id); |
249 | 13.0k | GC_Message_Array_Entry *array_entry = &gconn->send_array[idx]; |
250 | | |
251 | 13.0k | if (array_entry_is_empty(array_entry)) { |
252 | 0 | return true; |
253 | 0 | } |
254 | | |
255 | 13.0k | if (array_entry->message_id != message_id) { // wrap-around indicates a connection problem |
256 | 0 | LOGGER_DEBUG(log, "Wrap-around on message %llu", (unsigned long long)message_id); |
257 | 0 | return false; |
258 | 0 | } |
259 | | |
260 | 13.0k | clear_array_entry(array_entry); |
261 | | |
262 | | /* Put send_array_start in proper position */ |
263 | 13.0k | if (idx == gconn->send_array_start) { |
264 | 12.5k | const uint16_t end = gconn->send_message_id % GCC_BUFFER_SIZE; |
265 | | |
266 | 25.5k | while (array_entry_is_empty(&gconn->send_array[idx]) && gconn->send_array_start != end) { |
267 | 13.0k | gconn->send_array_start = (gconn->send_array_start + 1) % GCC_BUFFER_SIZE; |
268 | 13.0k | idx = (idx + 1) % GCC_BUFFER_SIZE; |
269 | 13.0k | } |
270 | 12.5k | } |
271 | | |
272 | 13.0k | return true; |
273 | 13.0k | } |
274 | | |
275 | | bool gcc_ip_port_is_set(const GC_Connection *gconn) |
276 | 86 | { |
277 | 86 | return ipport_isset(&gconn->addr.ip_port); |
278 | 86 | } |
279 | | |
280 | | void gcc_set_ip_port(GC_Connection *gconn, const IP_Port *ipp) |
281 | 1.00k | { |
282 | 1.00k | if (ipp != nullptr && ipport_isset(ipp)) { |
283 | 423 | gconn->addr.ip_port = *ipp; |
284 | 423 | } |
285 | 1.00k | } |
286 | | |
287 | | bool gcc_copy_tcp_relay(const Random *rng, Node_format *tcp_node, const GC_Connection *gconn) |
288 | 895 | { |
289 | 895 | if (gconn == nullptr || tcp_node == nullptr) { |
290 | 0 | return false; |
291 | 0 | } |
292 | | |
293 | 895 | if (gconn->tcp_relays_count == 0) { |
294 | 895 | return false; |
295 | 895 | } |
296 | | |
297 | 0 | const uint32_t rand_idx = random_range_u32(rng, gconn->tcp_relays_count); |
298 | |
|
299 | 0 | if (!ipport_isset(&gconn->connected_tcp_relays[rand_idx].ip_port)) { |
300 | 0 | return false; |
301 | 0 | } |
302 | | |
303 | 0 | *tcp_node = gconn->connected_tcp_relays[rand_idx]; |
304 | |
|
305 | 0 | return true; |
306 | 0 | } |
307 | | |
308 | | int gcc_save_tcp_relay(const Random *rng, GC_Connection *gconn, const Node_format *tcp_node) |
309 | 0 | { |
310 | 0 | if (gconn == nullptr || tcp_node == nullptr) { |
311 | 0 | return -1; |
312 | 0 | } |
313 | | |
314 | 0 | if (!ipport_isset(&tcp_node->ip_port)) { |
315 | 0 | return -1; |
316 | 0 | } |
317 | | |
318 | 0 | for (uint16_t i = 0; i < gconn->tcp_relays_count; ++i) { |
319 | 0 | if (pk_equal(gconn->connected_tcp_relays[i].public_key, tcp_node->public_key)) { |
320 | 0 | return -2; |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | 0 | uint32_t idx = gconn->tcp_relays_count; |
325 | |
|
326 | 0 | if (gconn->tcp_relays_count >= MAX_FRIEND_TCP_CONNECTIONS) { |
327 | 0 | idx = random_range_u32(rng, gconn->tcp_relays_count); |
328 | 0 | } else { |
329 | 0 | ++gconn->tcp_relays_count; |
330 | 0 | } |
331 | |
|
332 | 0 | gconn->connected_tcp_relays[idx] = *tcp_node; |
333 | |
|
334 | 0 | return 0; |
335 | 0 | } |
336 | | |
337 | | /** @brief Stores `data` of length `length` in the receive array for `gconn`. |
338 | | * |
339 | | * Return true on success. |
340 | | */ |
341 | | non_null(1, 2, 3) nullable(4) |
342 | | static bool store_in_recv_array(const Logger *log, const Mono_Time *mono_time, GC_Connection *gconn, |
343 | | const uint8_t *data, |
344 | | uint16_t length, uint8_t packet_type, uint64_t message_id) |
345 | 450 | { |
346 | 450 | const uint16_t idx = gcc_get_array_index(message_id); |
347 | 450 | GC_Message_Array_Entry *ary_entry = &gconn->recv_array[idx]; |
348 | | |
349 | 450 | return create_array_entry(log, mono_time, ary_entry, data, length, packet_type, message_id); |
350 | 450 | } |
351 | | |
352 | | /** |
353 | | * Reassembles a fragmented packet sequence ending with the data in the receive |
354 | | * array at slot `message_id - 1` and starting with the last found slot containing |
355 | | * a GP_FRAGMENT packet when searching backwards in the array. |
356 | | * |
357 | | * The fully reassembled packet is stored in `payload`, which must be passed as a |
358 | | * null pointer, and must be free'd by the caller. |
359 | | * |
360 | | * Return the length of the fully reassembled packet on success. |
361 | | * Return 0 on failure. |
362 | | */ |
363 | | non_null(1, 3) nullable(2) |
364 | | static uint16_t reassemble_packet(const Logger *log, GC_Connection *gconn, uint8_t **payload, uint64_t message_id) |
365 | 185 | { |
366 | 185 | uint16_t end_idx = gcc_get_array_index(message_id - 1); |
367 | 185 | uint16_t start_idx = end_idx; |
368 | 185 | uint16_t packet_length = 0; |
369 | | |
370 | 185 | GC_Message_Array_Entry *entry = &gconn->recv_array[end_idx]; |
371 | | |
372 | | // search backwards in recv array until we find an empty slot or a non-fragment packet type |
373 | 633 | while (!array_entry_is_empty(entry) && entry->packet_type == GP_FRAGMENT) { |
374 | 448 | assert(entry->data != nullptr); |
375 | 448 | assert(entry->data_length <= MAX_GC_PACKET_INCOMING_CHUNK_SIZE); |
376 | | |
377 | 448 | const uint16_t diff = packet_length + entry->data_length; |
378 | | |
379 | 448 | assert(diff > packet_length); // overflow check |
380 | 448 | packet_length = diff; |
381 | | |
382 | 448 | if (packet_length > MAX_GC_PACKET_SIZE) { |
383 | 0 | LOGGER_ERROR(log, "Payload of size %u exceeded max packet size", packet_length); // should never happen |
384 | 0 | return 0; |
385 | 0 | } |
386 | | |
387 | 448 | start_idx = start_idx > 0 ? start_idx - 1 : GCC_BUFFER_SIZE - 1; |
388 | 448 | entry = &gconn->recv_array[start_idx]; |
389 | | |
390 | 448 | if (start_idx == end_idx) { |
391 | 0 | LOGGER_ERROR(log, "Packet reassemble wrap-around"); |
392 | 0 | return 0; |
393 | 0 | } |
394 | 448 | } |
395 | | |
396 | 185 | if (packet_length == 0) { |
397 | 0 | return 0; |
398 | 0 | } |
399 | | |
400 | 185 | uint8_t *tmp_payload = (uint8_t *)malloc(packet_length); |
401 | | |
402 | 185 | if (tmp_payload == nullptr) { |
403 | 0 | LOGGER_ERROR(log, "Failed to allocate %u bytes for payload buffer", packet_length); |
404 | 0 | return 0; |
405 | 0 | } |
406 | | |
407 | 185 | start_idx = (start_idx + 1) % GCC_BUFFER_SIZE; |
408 | 185 | end_idx = (end_idx + 1) % GCC_BUFFER_SIZE; |
409 | | |
410 | 185 | uint16_t processed = 0; |
411 | | |
412 | 633 | for (uint16_t i = start_idx; i != end_idx; i = (i + 1) % GCC_BUFFER_SIZE) { |
413 | 448 | entry = &gconn->recv_array[i]; |
414 | | |
415 | 448 | assert(processed + entry->data_length <= packet_length); |
416 | 448 | memcpy(tmp_payload + processed, entry->data, entry->data_length); |
417 | 448 | processed += entry->data_length; |
418 | | |
419 | 448 | clear_array_entry(entry); |
420 | 448 | } |
421 | | |
422 | 185 | assert(*payload == nullptr); |
423 | 185 | *payload = tmp_payload; |
424 | | |
425 | 185 | return processed; |
426 | 185 | } |
427 | | |
428 | | int gcc_handle_packet_fragment(const GC_Session *c, GC_Chat *chat, uint32_t peer_number, |
429 | | GC_Connection *gconn, const uint8_t *chunk, uint16_t length, uint8_t packet_type, |
430 | | uint64_t message_id, void *userdata) |
431 | 633 | { |
432 | 633 | if (length > 0) { |
433 | 448 | if (!store_in_recv_array(chat->log, chat->mono_time, gconn, chunk, length, packet_type, message_id)) { |
434 | 0 | return -1; |
435 | 0 | } |
436 | | |
437 | 448 | gcc_set_recv_message_id(gconn, gconn->received_message_id + 1); |
438 | 448 | gconn->last_chunk_id = message_id; |
439 | | |
440 | 448 | return 1; |
441 | 448 | } |
442 | | |
443 | 185 | uint8_t sender_pk[ENC_PUBLIC_KEY_SIZE]; |
444 | 185 | memcpy(sender_pk, get_enc_key(gconn->addr.public_key), ENC_PUBLIC_KEY_SIZE); |
445 | | |
446 | 185 | uint8_t *payload = nullptr; |
447 | 185 | const uint16_t processed_len = reassemble_packet(chat->log, gconn, &payload, message_id); |
448 | | |
449 | 185 | if (processed_len == 0) { |
450 | 0 | free(payload); |
451 | 0 | return -1; |
452 | 0 | } |
453 | | |
454 | 185 | if (!handle_gc_lossless_helper(c, chat, peer_number, payload + 1, processed_len - 1, payload[0], userdata)) { |
455 | 0 | free(payload); |
456 | 0 | return -1; |
457 | 0 | } |
458 | | |
459 | | /* peer number can change from peer add operations in packet handlers */ |
460 | 185 | peer_number = get_peer_number_of_enc_pk(chat, sender_pk, false); |
461 | 185 | gconn = get_gc_connection(chat, peer_number); |
462 | | |
463 | 185 | if (gconn == nullptr) { |
464 | 0 | free(payload); |
465 | 0 | return 0; |
466 | 0 | } |
467 | | |
468 | 185 | gcc_set_recv_message_id(gconn, gconn->received_message_id + 1); |
469 | 185 | gconn->last_chunk_id = 0; |
470 | | |
471 | 185 | free(payload); |
472 | | |
473 | 185 | return 0; |
474 | 185 | } |
475 | | |
476 | | int gcc_handle_received_message(const Logger *log, const Mono_Time *mono_time, GC_Connection *gconn, |
477 | | const uint8_t *data, uint16_t length, uint8_t packet_type, uint64_t message_id, |
478 | | bool direct_conn) |
479 | 13.8k | { |
480 | 13.8k | if (direct_conn) { |
481 | 13.8k | gconn->last_received_direct_time = mono_time_get(mono_time); |
482 | 13.8k | } |
483 | | |
484 | | /* Appears to be a duplicate packet so we discard it */ |
485 | 13.8k | if (message_id < gconn->received_message_id + 1) { |
486 | 454 | return 0; |
487 | 454 | } |
488 | | |
489 | 13.3k | if (packet_type == GP_FRAGMENT) { // we handle packet fragments as a special case |
490 | 633 | return 3; |
491 | 633 | } |
492 | | |
493 | | /* we're missing an older message from this peer so we store it in recv_array */ |
494 | 12.7k | if (message_id > gconn->received_message_id + 1) { |
495 | 2 | if (!store_in_recv_array(log, mono_time, gconn, data, length, packet_type, message_id)) { |
496 | 1 | return -1; |
497 | 1 | } |
498 | | |
499 | 1 | return 1; |
500 | 2 | } |
501 | | |
502 | 12.7k | gcc_set_recv_message_id(gconn, gconn->received_message_id + 1); |
503 | | |
504 | 12.7k | return 2; |
505 | 12.7k | } |
506 | | |
507 | | /** @brief Handles peer_number's array entry with appropriate handler and clears it from array. |
508 | | * |
509 | | * This function increments the received message ID for `gconn`. |
510 | | * |
511 | | * Return true on success. |
512 | | */ |
513 | | non_null(1, 2, 3, 5) nullable(6) |
514 | | static bool process_recv_array_entry(const GC_Session *c, GC_Chat *chat, GC_Connection *gconn, uint32_t peer_number, |
515 | | GC_Message_Array_Entry *const array_entry, void *userdata) |
516 | 0 | { |
517 | 0 | uint8_t sender_pk[ENC_PUBLIC_KEY_SIZE]; |
518 | 0 | memcpy(sender_pk, get_enc_key(gconn->addr.public_key), ENC_PUBLIC_KEY_SIZE); |
519 | |
|
520 | 0 | const bool ret = handle_gc_lossless_helper(c, chat, peer_number, array_entry->data, array_entry->data_length, |
521 | 0 | array_entry->packet_type, userdata); |
522 | | |
523 | | /* peer number can change from peer add operations in packet handlers */ |
524 | 0 | peer_number = get_peer_number_of_enc_pk(chat, sender_pk, false); |
525 | 0 | gconn = get_gc_connection(chat, peer_number); |
526 | |
|
527 | 0 | clear_array_entry(array_entry); |
528 | |
|
529 | 0 | if (gconn == nullptr) { |
530 | 0 | return true; |
531 | 0 | } |
532 | | |
533 | 0 | if (!ret) { |
534 | 0 | gc_send_message_ack(chat, gconn, array_entry->message_id, GR_ACK_REQ); |
535 | 0 | return false; |
536 | 0 | } |
537 | | |
538 | 0 | gc_send_message_ack(chat, gconn, array_entry->message_id, GR_ACK_RECV); |
539 | |
|
540 | 0 | gcc_set_recv_message_id(gconn, gconn->received_message_id + 1); |
541 | |
|
542 | 0 | return true; |
543 | 0 | } |
544 | | |
545 | | void gcc_check_recv_array(const GC_Session *c, GC_Chat *chat, GC_Connection *gconn, uint32_t peer_number, |
546 | | void *userdata) |
547 | 25.3k | { |
548 | 25.3k | if (gconn->last_chunk_id != 0) { // dont check array if we have an unfinished fragment sequence |
549 | 0 | return; |
550 | 0 | } |
551 | | |
552 | 25.3k | const uint16_t idx = (gconn->received_message_id + 1) % GCC_BUFFER_SIZE; |
553 | 25.3k | GC_Message_Array_Entry *const array_entry = &gconn->recv_array[idx]; |
554 | | |
555 | 25.3k | if (!array_entry_is_empty(array_entry)) { |
556 | 0 | process_recv_array_entry(c, chat, gconn, peer_number, array_entry, userdata); |
557 | 0 | } |
558 | 25.3k | } |
559 | | |
560 | | void gcc_resend_packets(const GC_Chat *chat, GC_Connection *gconn) |
561 | 25.3k | { |
562 | 25.3k | const uint64_t tm = mono_time_get(chat->mono_time); |
563 | 25.3k | const uint16_t start = gconn->send_array_start; |
564 | 25.3k | const uint16_t end = gconn->send_message_id % GCC_BUFFER_SIZE; |
565 | | |
566 | 25.3k | GC_Message_Array_Entry *array_entry = &gconn->send_array[start]; |
567 | | |
568 | 25.3k | if (array_entry_is_empty(array_entry)) { |
569 | 22.4k | return; |
570 | 22.4k | } |
571 | | |
572 | 2.95k | if (mono_time_is_timeout(chat->mono_time, array_entry->time_added, GC_CONFIRMED_PEER_TIMEOUT)) { |
573 | 0 | gcc_mark_for_deletion(gconn, chat->tcp_conn, GC_EXIT_TYPE_TIMEOUT, nullptr, 0); |
574 | 0 | LOGGER_DEBUG(chat->log, "Send array stuck; timing out peer"); |
575 | 0 | return; |
576 | 0 | } |
577 | | |
578 | 26.1k | for (uint16_t i = start; i != end; i = (i + 1) % GCC_BUFFER_SIZE) { |
579 | 23.1k | array_entry = &gconn->send_array[i]; |
580 | | |
581 | 23.1k | if (array_entry_is_empty(array_entry)) { |
582 | 4.24k | continue; |
583 | 4.24k | } |
584 | | |
585 | 18.9k | if (tm == array_entry->last_send_try) { |
586 | 15.4k | continue; |
587 | 15.4k | } |
588 | | |
589 | 3.49k | const uint64_t delta = array_entry->last_send_try - array_entry->time_added; |
590 | 3.49k | array_entry->last_send_try = tm; |
591 | | |
592 | | /* if this occurrs less than once per second this won't be reliable */ |
593 | 3.49k | if (delta > 1 && is_power_of_2(delta)) { |
594 | 481 | gcc_encrypt_and_send_lossless_packet(chat, gconn, array_entry->data, array_entry->data_length, |
595 | 481 | array_entry->message_id, array_entry->packet_type); |
596 | 481 | } |
597 | 3.49k | } |
598 | 2.95k | } |
599 | | |
600 | | bool gcc_send_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint8_t *packet, uint16_t length) |
601 | 27.7k | { |
602 | 27.7k | if (packet == nullptr || length == 0) { |
603 | 0 | return false; |
604 | 0 | } |
605 | | |
606 | 27.7k | bool direct_send_attempt = false; |
607 | | |
608 | 27.7k | if (gcc_direct_conn_is_possible(chat, gconn)) { |
609 | 27.7k | if (gcc_conn_is_direct(chat->mono_time, gconn)) { |
610 | 27.3k | return (uint16_t) sendpacket(chat->net, &gconn->addr.ip_port, packet, length) == length; |
611 | 27.3k | } |
612 | | |
613 | 380 | if ((uint16_t) sendpacket(chat->net, &gconn->addr.ip_port, packet, length) == length) { |
614 | 379 | direct_send_attempt = true; |
615 | 379 | } |
616 | 380 | } |
617 | | |
618 | 380 | const int ret = send_packet_tcp_connection(chat->tcp_conn, gconn->tcp_connection_num, packet, length); |
619 | 380 | return ret == 0 || direct_send_attempt; |
620 | 27.7k | } |
621 | | |
622 | | int gcc_encrypt_and_send_lossless_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint8_t *data, |
623 | | uint16_t length, uint64_t message_id, uint8_t packet_type) |
624 | 14.1k | { |
625 | 14.1k | const uint16_t packet_size = gc_get_wrapped_packet_size(length, NET_PACKET_GC_LOSSLESS); |
626 | 14.1k | uint8_t *packet = (uint8_t *)malloc(packet_size); |
627 | | |
628 | 14.1k | if (packet == nullptr) { |
629 | 9 | LOGGER_ERROR(chat->log, "Failed to allocate memory for packet buffer"); |
630 | 9 | return -1; |
631 | 9 | } |
632 | | |
633 | 14.1k | const int enc_len = group_packet_wrap( |
634 | 14.1k | chat->log, chat->rng, chat->self_public_key, gconn->session_shared_key, packet, |
635 | 14.1k | packet_size, data, length, message_id, packet_type, NET_PACKET_GC_LOSSLESS); |
636 | | |
637 | 14.1k | if (enc_len < 0) { |
638 | 30 | LOGGER_ERROR(chat->log, "Failed to wrap packet (type: 0x%02x, error: %d)", packet_type, enc_len); |
639 | 30 | free(packet); |
640 | 30 | return -1; |
641 | 30 | } |
642 | | |
643 | 14.1k | if (!gcc_send_packet(chat, gconn, packet, (uint16_t)enc_len)) { |
644 | 12 | LOGGER_DEBUG(chat->log, "Failed to send packet (type: 0x%02x, enc_len: %d)", packet_type, enc_len); |
645 | 12 | free(packet); |
646 | 12 | return -2; |
647 | 12 | } |
648 | | |
649 | 14.1k | free(packet); |
650 | | |
651 | 14.1k | return 0; |
652 | 14.1k | } |
653 | | |
654 | | void gcc_make_session_shared_key(GC_Connection *gconn, const uint8_t *sender_pk) |
655 | 372 | { |
656 | 372 | encrypt_precompute(sender_pk, gconn->session_secret_key, gconn->session_shared_key); |
657 | 372 | } |
658 | | |
659 | | bool gcc_conn_is_direct(const Mono_Time *mono_time, const GC_Connection *gconn) |
660 | 53.4k | { |
661 | 53.4k | return GCC_UDP_DIRECT_TIMEOUT + gconn->last_received_direct_time > mono_time_get(mono_time); |
662 | 53.4k | } |
663 | | |
664 | | bool gcc_direct_conn_is_possible(const GC_Chat *chat, const GC_Connection *gconn) |
665 | 28.2k | { |
666 | 28.2k | return !net_family_is_unspec(gconn->addr.ip_port.ip.family) && !net_family_is_unspec(net_family(chat->net)); |
667 | 28.2k | } |
668 | | |
669 | | void gcc_mark_for_deletion(GC_Connection *gconn, TCP_Connections *tcp_conn, Group_Exit_Type type, |
670 | | const uint8_t *part_message, uint16_t length) |
671 | 134 | { |
672 | 134 | if (gconn == nullptr) { |
673 | 0 | return; |
674 | 0 | } |
675 | | |
676 | 134 | if (gconn->pending_delete) { |
677 | 0 | return; |
678 | 0 | } |
679 | | |
680 | 134 | gconn->pending_delete = true; |
681 | 134 | gconn->exit_info.exit_type = type; |
682 | | |
683 | 134 | kill_tcp_connection_to(tcp_conn, gconn->tcp_connection_num); |
684 | | |
685 | 134 | if (length > 0 && length <= MAX_GC_PART_MESSAGE_SIZE && part_message != nullptr) { |
686 | 1 | memcpy(gconn->exit_info.part_message, part_message, length); |
687 | 1 | gconn->exit_info.length = length; |
688 | 1 | } |
689 | 134 | } |
690 | | |
691 | | void gcc_peer_cleanup(GC_Connection *gconn) |
692 | 615 | { |
693 | 5.03M | for (size_t i = 0; i < GCC_BUFFER_SIZE; ++i) { |
694 | 5.03M | free(gconn->send_array[i].data); |
695 | 5.03M | free(gconn->recv_array[i].data); |
696 | 5.03M | } |
697 | | |
698 | 615 | free(gconn->recv_array); |
699 | 615 | free(gconn->send_array); |
700 | | |
701 | 615 | crypto_memunlock(gconn->session_secret_key, sizeof(gconn->session_secret_key)); |
702 | 615 | crypto_memunlock(gconn->session_shared_key, sizeof(gconn->session_shared_key)); |
703 | 615 | crypto_memzero(gconn, sizeof(GC_Connection)); |
704 | 615 | } |
705 | | |
706 | | void gcc_cleanup(const GC_Chat *chat) |
707 | 2.61k | { |
708 | 3.09k | for (uint32_t i = 0; i < chat->numpeers; ++i) { |
709 | 481 | GC_Connection *gconn = get_gc_connection(chat, i); |
710 | 481 | assert(gconn != nullptr); |
711 | | |
712 | 481 | gcc_peer_cleanup(gconn); |
713 | 481 | } |
714 | 2.61k | } |