Coverage Report

Created: 2026-10-24 08:16

/work/auto_tests/group_announce_test.c
Line
Count
Source
1
/* SPDX-License-Identifier: GPL-3.0-or-later
2
 * Copyright © 2026 The TokTok team.
3
 */
4
5
/**
6
 * Tests GCA (group chat announce) response size bounds.
7
 *
8
 * Sends a GCA announce request to a server that has a full DHT node list and
9
 * a full GCA announce list, then verifies that the response fits within
10
 * GCA_ANNOUNCE_RESPONSE_MAX_SIZE.
11
 */
12
13
#include <stdbool.h>
14
#include <stdint.h>
15
#include <stdlib.h>
16
#include <string.h>
17
18
#include "../toxcore/DHT.h"
19
#include "../toxcore/crypto_core.h"
20
#include "../toxcore/group_announce.h"
21
#include "../toxcore/group_onion_announce.h"
22
#include "../toxcore/logger.h"
23
#include "../toxcore/mono_time.h"
24
#include "../toxcore/net.h"
25
#include "../toxcore/network.h"
26
#include "../toxcore/onion.h"
27
#include "../toxcore/onion_announce.h"
28
#include "../toxcore/os_memory.h"
29
#include "../toxcore/os_random.h"
30
#include "auto_test_support.h"
31
#include "check_compat.h"
32
33
#ifndef USE_IPV6
34
#define USE_IPV6 1
35
#endif
36
37
static inline IP get_loopback(void)
38
136
{
39
136
    IP ip;
40
#if USE_IPV6
41
    ip.family = net_family_ipv6();
42
    ip.ip.v6 = get_ip6_loopback();
43
#else
44
136
    ip.family = net_family_ipv4();
45
136
    ip.ip.v4 = get_ip4_loopback();
46
136
#endif
47
136
    return ip;
48
136
}
49
50
typedef struct {
51
    bool     received;
52
    uint16_t length;
53
} Recv3_State;
54
55
static int handle_recv_3(void *object, const IP_Port *source, const uint8_t *packet,
56
                         uint16_t length, void *userdata)
57
3
{
58
3
    Recv3_State *state = (Recv3_State *)object;
59
3
    state->received = true;
60
3
    state->length   = length;
61
3
    return 0;
62
3
}
63
64
/** Fill @p ann with a maximum-size IPv6 announce (IPv6 ip_port + IPv6 TCP relay). */
65
static void fill_max_public_announce(const Random *rng, GC_Public_Announce *ann,
66
                                     const uint8_t *chat_pk)
67
73
{
68
73
    memcpy(ann->chat_public_key, chat_pk, ENC_PUBLIC_KEY_SIZE);
69
73
    random_bytes(rng, ann->base_announce.peer_public_key, ENC_PUBLIC_KEY_SIZE);
70
71
73
    ann->base_announce.ip_port.ip.family = net_family_ipv6();
72
73
    random_bytes(rng, ann->base_announce.ip_port.ip.ip.v6.uint8,
73
73
                 sizeof(ann->base_announce.ip_port.ip.ip.v6.uint8));
74
73
    ann->base_announce.ip_port.port   = net_htons(33445);
75
73
    ann->base_announce.ip_port_is_set = true;
76
77
73
    ann->base_announce.tcp_relays_count = GCA_MAX_ANNOUNCED_TCP_RELAYS;
78
73
    random_bytes(rng, ann->base_announce.tcp_relays[0].public_key,
79
73
                 CRYPTO_PUBLIC_KEY_SIZE);
80
73
    ann->base_announce.tcp_relays[0].ip_port.ip.family = net_family_tcp_ipv6();
81
73
    random_bytes(rng, ann->base_announce.tcp_relays[0].ip_port.ip.ip.v6.uint8,
82
73
                 sizeof(ann->base_announce.tcp_relays[0].ip_port.ip.ip.v6.uint8));
83
73
    ann->base_announce.tcp_relays[0].ip_port.port = net_htons(33445);
84
73
}
85
86
/** Fill @p ann with a small IPv4-only announce (fits within GCA_ANNOUNCE_MAX_SIZE). */
87
static void fill_small_public_announce(const Random *rng, GC_Public_Announce *ann,
88
                                       const uint8_t *chat_pk)
89
12
{
90
12
    memcpy(ann->chat_public_key, chat_pk, ENC_PUBLIC_KEY_SIZE);
91
12
    random_bytes(rng, ann->base_announce.peer_public_key, ENC_PUBLIC_KEY_SIZE);
92
93
12
    ann->base_announce.ip_port.ip.family = net_family_ipv4();
94
12
    ann->base_announce.ip_port.ip.ip.v4  = get_ip4_loopback();
95
12
    ann->base_announce.ip_port.port       = net_htons(33445);
96
12
    ann->base_announce.ip_port_is_set     = true;
97
12
    ann->base_announce.tcp_relays_count   = 0;
98
12
}
99
100
static void test_gca_announce_response_size(void)
101
40
{
102
40
    const Memory  *mem = os_memory();
103
40
    ck_assert(mem != nullptr);
104
40
    const Random  *rng = os_random();
105
40
    ck_assert(rng != nullptr);
106
40
    const Network *ns  = os_network();
107
40
    ck_assert(ns != nullptr);
108
109
    /* ------------------------------------------------------------------ */
110
    /* Server node: Onion_Announce with GCA callback registered.          */
111
    /* ------------------------------------------------------------------ */
112
40
    uint32_t srv_idx = 1;
113
40
    uint32_t cli_idx = 2;
114
115
40
    Logger *srv_log = logger_new(mem);
116
40
    ck_assert(srv_log != nullptr);
117
40
    logger_callback_log(srv_log, print_debug_logger, nullptr, &srv_idx);
118
119
40
    Mono_Time *srv_mono = mono_time_new(mem, nullptr, nullptr);
120
40
    ck_assert(srv_mono != nullptr);
121
40
    mono_time_update(srv_mono);
122
123
40
    IP ip = get_loopback();
124
125
40
    Networking_Core *srv_net = new_networking_ex(
126
40
                                   srv_log, mem, ns, &ip, 36700,
127
40
                                   36700 + (TOX_PORTRANGE_TO - TOX_PORTRANGE_FROM), nullptr);
128
40
    ck_assert(srv_net != nullptr);
129
130
38
    DHT *srv_dht = new_dht(srv_log, mem, rng, ns, srv_mono, srv_net, true, false);
131
38
    ck_assert(srv_dht != nullptr);
132
133
26
    Onion *srv_onion = new_onion(srv_log, mem, srv_mono, rng, srv_dht, srv_net);
134
26
    ck_assert(srv_onion != nullptr);
135
136
23
    Onion_Announce *srv_onion_a = new_onion_announce(
137
23
                                      srv_log, mem, rng, srv_mono, srv_dht, srv_net);
138
23
    ck_assert(srv_onion_a != nullptr);
139
140
20
    GC_Announces_List *gca_list = new_gca_list(mem);
141
20
    ck_assert(gca_list != nullptr);
142
143
19
    gca_onion_init(gca_list, srv_onion_a);
144
145
    /* Pre-fill the GCA list with GCA_MAX_SENT_ANNOUNCES full-size IPv6
146
     * announces so the server returns a maximally-populated response. */
147
19
    uint8_t chat_pk[ENC_PUBLIC_KEY_SIZE];
148
19
    random_bytes(rng, chat_pk, sizeof(chat_pk));
149
150
91
    for (uint32_t i = 0; i < GCA_MAX_SENT_ANNOUNCES; ++i) {
151
73
        GC_Public_Announce ann;
152
73
        fill_max_public_announce(rng, &ann, chat_pk);
153
73
        mono_time_update(srv_mono);
154
73
        GC_Peer_Announce *peer_ann = gca_add_announce(mem, srv_mono, gca_list, &ann);
155
73
        ck_assert_msg(peer_ann != nullptr, "Failed to add pre-filled GCA announce %u", i);
156
73
    }
157
158
    /* Inject MAX_SENT_NODES fake LAN entries so get_close_nodes() returns
159
     * a full node list alongside the GCA announces. */
160
90
    for (uint32_t i = 0; i < MAX_SENT_NODES; ++i) {
161
72
        uint8_t fake_pk[CRYPTO_PUBLIC_KEY_SIZE];
162
72
        random_bytes(rng, fake_pk, sizeof(fake_pk));
163
72
        IP_Port fake_ipp;
164
72
        fake_ipp.ip.family         = net_family_ipv4();
165
72
        fake_ipp.ip.ip.v4.uint8[0] = 10;
166
72
        fake_ipp.ip.ip.v4.uint8[1] = 0;
167
72
        fake_ipp.ip.ip.v4.uint8[2] = 0;
168
72
        fake_ipp.ip.ip.v4.uint8[3] = (uint8_t)(i + 1);
169
72
        fake_ipp.port              = net_htons(33445 + i);
170
72
        addto_lists(srv_dht, &fake_ipp, fake_pk);
171
72
    }
172
173
    /* ------------------------------------------------------------------ */
174
    /* Client node: raw Networking_Core with a RECV_3 capture handler.    */
175
    /* ------------------------------------------------------------------ */
176
18
    Logger *cli_log = logger_new(mem);
177
18
    ck_assert(cli_log != nullptr);
178
17
    logger_callback_log(cli_log, print_debug_logger, nullptr, &cli_idx);
179
180
17
    Mono_Time *cli_mono = mono_time_new(mem, nullptr, nullptr);
181
17
    ck_assert(cli_mono != nullptr);
182
183
15
    Networking_Core *cli_net = new_networking_ex(
184
15
                                   cli_log, mem, ns, &ip, 36701,
185
15
                                   36701 + (TOX_PORTRANGE_TO - TOX_PORTRANGE_FROM), nullptr);
186
15
    ck_assert(cli_net != nullptr);
187
188
12
    Recv3_State state = {false, 0};
189
12
    networking_registerhandler(cli_net, NET_PACKET_ONION_RECV_3,
190
12
                               handle_recv_3, &state);
191
192
    /* ------------------------------------------------------------------ */
193
    /* Build a GCA announce request and send it directly to the server.   */
194
    /* ------------------------------------------------------------------ */
195
196
    /* Use a small IPv4-only public announce as the gc_data payload so that
197
     * gc_data_length fits within the GCA_ANNOUNCE_MAX_SIZE limit. */
198
12
    GC_Public_Announce my_ann;
199
12
    fill_small_public_announce(rng, &my_ann, chat_pk);
200
201
12
    uint8_t gc_data[GCA_PUBLIC_ANNOUNCE_MAX_SIZE];
202
12
    const int gc_data_len = gca_pack_public_announce(
203
12
                                srv_log, gc_data, sizeof(gc_data), &my_ann);
204
12
    ck_assert_msg(gc_data_len > 0, "Failed to pack public announce");
205
12
    ck_assert_msg((uint16_t)gc_data_len <= GCA_ANNOUNCE_MAX_SIZE,
206
12
                  "gc_data_len %d exceeds GCA_ANNOUNCE_MAX_SIZE", gc_data_len);
207
208
12
    uint8_t cli_pk[CRYPTO_PUBLIC_KEY_SIZE];
209
12
    uint8_t cli_sk[CRYPTO_SECRET_KEY_SIZE];
210
12
    crypto_new_keypair(rng, cli_pk, cli_sk);
211
212
12
    uint8_t data_pk[CRYPTO_PUBLIC_KEY_SIZE];
213
12
    uint8_t data_sk[CRYPTO_SECRET_KEY_SIZE];
214
12
    crypto_new_keypair(rng, data_pk, data_sk);
215
216
12
    const uint8_t zeroes[ONION_PING_ID_SIZE] = {0};
217
12
    const uint64_t sendback = 0x0102030405060708ULL;
218
219
    /* The request buffer must also hold ONION_RETURN_3 return-path bytes
220
     * at the tail, as required by the announce request handler. */
221
12
    const uint16_t req_cap = ONION_ANNOUNCE_REQUEST_MAX_SIZE + ONION_RETURN_3;
222
12
    uint8_t *req_buf = (uint8_t *)malloc(req_cap);
223
12
    ck_assert(req_buf != nullptr);
224
225
11
    const int req_len = create_gca_announce_request(
226
11
                            mem, rng, req_buf, ONION_ANNOUNCE_REQUEST_MAX_SIZE,
227
11
                            dht_get_self_public_key(srv_dht),
228
11
                            cli_pk, cli_sk,
229
11
                            zeroes, cli_pk, data_pk, sendback,
230
11
                            gc_data, (uint16_t)gc_data_len);
231
11
    ck_assert_msg(req_len > 0, "Failed to create GCA announce request");
232
233
10
    memset(req_buf + req_len, 0, ONION_RETURN_3);
234
10
    const uint16_t total_len = (uint16_t)(req_len + ONION_RETURN_3);
235
236
10
    const IP_Port srv_ipp = {ip, net_port(srv_net)};
237
10
    ck_assert_msg(sendpacket(cli_net, &srv_ipp, req_buf, total_len) == total_len,
238
10
                  "Failed to send GCA announce request");
239
9
    free(req_buf);
240
241
    /* ------------------------------------------------------------------ */
242
    /* Poll until response received or 1-second timeout (200 × 5 ms).    */
243
    /* ------------------------------------------------------------------ */
244
1.21k
    for (int i = 0; i < 200 && !state.received; ++i) {
245
1.20k
        mono_time_update(srv_mono);
246
1.20k
        mono_time_update(cli_mono);
247
1.20k
        networking_poll(srv_net, nullptr);
248
1.20k
        networking_poll(cli_net, nullptr);
249
1.20k
        c_sleep(5);
250
1.20k
    }
251
252
9
    ck_assert_msg(state.received, "No GCA announce response received within timeout");
253
254
    /* The ONION_RECV_3 packet layout:
255
     *   [1 byte type] [ONION_RETURN_3 bytes return path] [payload bytes] */
256
3
    ck_assert_msg(state.length > (uint16_t)(1 + ONION_RETURN_3),
257
3
                  "RECV_3 packet too short to contain any payload: %u", state.length);
258
259
3
    const uint16_t resp_len = state.length - (uint16_t)(1 + ONION_RETURN_3);
260
261
3
    ck_assert_msg(resp_len >= ONION_ANNOUNCE_RESPONSE_MIN_SIZE,
262
3
                  "announce response too small: %u (min %u)",
263
3
                  resp_len, (unsigned int)ONION_ANNOUNCE_RESPONSE_MIN_SIZE);
264
265
3
    ck_assert_msg(resp_len <= GCA_ANNOUNCE_RESPONSE_MAX_SIZE,
266
3
                  "announce response too large: %u (max %u)",
267
3
                  resp_len, (unsigned int)GCA_ANNOUNCE_RESPONSE_MAX_SIZE);
268
269
3
    printf("GCA announce response payload: %u bytes "
270
3
           "(min: %u, max: %u)\n",
271
3
           resp_len,
272
3
           (unsigned int)ONION_ANNOUNCE_RESPONSE_MIN_SIZE,
273
3
           (unsigned int)GCA_ANNOUNCE_RESPONSE_MAX_SIZE);
274
275
    /* ------------------------------------------------------------------ */
276
    /* Cleanup (reverse order of creation).                               */
277
    /* ------------------------------------------------------------------ */
278
3
    networking_registerhandler(cli_net, NET_PACKET_ONION_RECV_3, nullptr, nullptr);
279
3
    kill_networking(cli_net);
280
3
    mono_time_free(mem, cli_mono);
281
3
    logger_kill(cli_log);
282
283
3
    kill_gca(gca_list);
284
3
    kill_onion_announce(srv_onion_a);
285
3
    kill_onion(srv_onion);
286
3
    kill_dht(srv_dht);
287
3
    kill_networking(srv_net);
288
3
    mono_time_free(mem, srv_mono);
289
3
    logger_kill(srv_log);
290
3
}
291
292
static void basic_gca_announce_tests(void)
293
40
{
294
40
    test_gca_announce_response_size();
295
40
}
296
297
int main(void)
298
84
{
299
84
    setvbuf(stdout, nullptr, _IONBF, 0);
300
301
84
    basic_gca_announce_tests();
302
84
    return 0;
303
84
}