Coverage Report

Created: 2025-10-08 19:34

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