/work/toxcore/group_announce.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 | | #include "group_announce.h" |
7 | | |
8 | | #include <string.h> |
9 | | |
10 | | #include "DHT.h" |
11 | | #include "attributes.h" |
12 | | #include "ccompat.h" |
13 | | #include "crypto_core.h" |
14 | | #include "logger.h" |
15 | | #include "mem.h" |
16 | | #include "mono_time.h" |
17 | | #include "network.h" |
18 | | |
19 | | /** |
20 | | * Removes `announces` from `gc_announces_list`. |
21 | | */ |
22 | | static void remove_announces(GC_Announces_List *_Nonnull gc_announces_list, GC_Announces *_Nonnull announces) |
23 | 210 | { |
24 | 210 | if (announces == nullptr || gc_announces_list == nullptr) { |
25 | 0 | return; |
26 | 0 | } |
27 | | |
28 | 210 | if (announces->prev_announce != nullptr) { |
29 | 67 | announces->prev_announce->next_announce = announces->next_announce; |
30 | 143 | } else { |
31 | 143 | gc_announces_list->root_announces = announces->next_announce; |
32 | 143 | } |
33 | | |
34 | 210 | if (announces->next_announce != nullptr) { |
35 | 113 | announces->next_announce->prev_announce = announces->prev_announce; |
36 | 113 | } |
37 | | |
38 | 210 | mem_delete(gc_announces_list->mem, announces); |
39 | 210 | } |
40 | | |
41 | | /** |
42 | | * Returns the announce designated by `chat_id`. |
43 | | * Returns null if no announce is found. |
44 | | */ |
45 | | static GC_Announces *_Nullable get_announces_by_chat_id(const GC_Announces_List *_Nonnull gc_announces_list, const uint8_t *_Nonnull chat_id) |
46 | 5.55k | { |
47 | 5.55k | GC_Announces *announces = gc_announces_list->root_announces; |
48 | | |
49 | 7.66k | while (announces != nullptr) { |
50 | 6.27k | if (memcmp(announces->chat_id, chat_id, CHAT_ID_SIZE) == 0) { |
51 | 4.16k | return announces; |
52 | 4.16k | } |
53 | | |
54 | 2.11k | announces = announces->next_announce; |
55 | 2.11k | } |
56 | | |
57 | 1.38k | return nullptr; |
58 | 5.55k | } |
59 | | |
60 | | int gca_get_announces(const GC_Announces_List *gc_announces_list, GC_Announce *gc_announces, uint8_t max_nodes, |
61 | | const uint8_t *chat_id, const uint8_t *except_public_key) |
62 | 2.40k | { |
63 | 2.40k | if (gc_announces == nullptr || gc_announces_list == nullptr || chat_id == nullptr || max_nodes == 0 |
64 | 2.40k | || except_public_key == nullptr) { |
65 | 99 | return -1; |
66 | 99 | } |
67 | | |
68 | 2.30k | const GC_Announces *announces = get_announces_by_chat_id(gc_announces_list, chat_id); |
69 | | |
70 | 2.30k | if (announces == nullptr) { |
71 | 427 | return 0; |
72 | 427 | } |
73 | | |
74 | 1.87k | uint16_t added_count = 0; |
75 | | |
76 | 17.0k | for (size_t i = 0; i < announces->index && i < GCA_MAX_SAVED_ANNOUNCES_PER_GC && added_count < max_nodes; ++i) { |
77 | 15.1k | const size_t index = i % GCA_MAX_SAVED_ANNOUNCES_PER_GC; |
78 | | |
79 | 15.1k | if (memcmp(except_public_key, announces->peer_announces[index].base_announce.peer_public_key, |
80 | 15.1k | ENC_PUBLIC_KEY_SIZE) == 0) { |
81 | 4.22k | continue; |
82 | 4.22k | } |
83 | | |
84 | 10.9k | bool already_added = false; |
85 | | |
86 | 24.7k | for (size_t j = 0; j < added_count; ++j) { |
87 | 18.8k | if (memcmp(gc_announces[j].peer_public_key, announces->peer_announces[index].base_announce.peer_public_key, |
88 | 18.8k | ENC_PUBLIC_KEY_SIZE) == 0) { |
89 | 5.04k | already_added = true; |
90 | 5.04k | break; |
91 | 5.04k | } |
92 | 18.8k | } |
93 | | |
94 | 10.9k | if (!already_added) { |
95 | 5.87k | gc_announces[added_count] = announces->peer_announces[index].base_announce; |
96 | 5.87k | ++added_count; |
97 | 5.87k | } |
98 | 10.9k | } |
99 | | |
100 | 1.87k | return added_count; |
101 | 2.30k | } |
102 | | |
103 | | uint16_t gca_pack_announces_list_size(uint16_t count) |
104 | 2 | { |
105 | 2 | return count * GCA_ANNOUNCE_MAX_SIZE; |
106 | 2 | } |
107 | | |
108 | | int gca_pack_announce(const Logger *log, uint8_t *data, uint16_t length, const GC_Announce *announce) |
109 | 6.03k | { |
110 | 6.03k | if (length < GCA_ANNOUNCE_MAX_SIZE) { |
111 | 5 | LOGGER_ERROR(log, "Invalid announce length: %u", length); |
112 | 5 | return -1; |
113 | 5 | } |
114 | | |
115 | 6.03k | if (data == nullptr) { |
116 | 0 | LOGGER_ERROR(log, "data is null"); |
117 | 0 | return -1; |
118 | 0 | } |
119 | | |
120 | 6.03k | if (announce == nullptr) { |
121 | 0 | LOGGER_ERROR(log, "announce is null"); |
122 | 0 | return -1; |
123 | 0 | } |
124 | | |
125 | 6.03k | uint16_t offset = 0; |
126 | 6.03k | memcpy(data + offset, announce->peer_public_key, ENC_PUBLIC_KEY_SIZE); |
127 | 6.03k | offset += ENC_PUBLIC_KEY_SIZE; |
128 | | |
129 | 6.03k | data[offset] = announce->ip_port_is_set ? 1 : 0; |
130 | 6.03k | ++offset; |
131 | | |
132 | 6.03k | data[offset] = announce->tcp_relays_count; |
133 | 6.03k | ++offset; |
134 | | |
135 | 6.03k | if (!announce->ip_port_is_set && announce->tcp_relays_count == 0) { |
136 | 59 | LOGGER_ERROR(log, "Failed to pack announce: no valid ip_port or tcp relay"); |
137 | 59 | return -1; |
138 | 59 | } |
139 | | |
140 | 5.97k | if (announce->ip_port_is_set) { |
141 | 5.63k | const int ip_port_length = pack_ip_port(log, data + offset, length - offset, &announce->ip_port); |
142 | | |
143 | 5.63k | if (ip_port_length == -1) { |
144 | 0 | LOGGER_ERROR(log, "Failed to pack ip_port"); |
145 | 0 | return -1; |
146 | 0 | } |
147 | | |
148 | 5.63k | offset += ip_port_length; |
149 | 5.63k | } |
150 | | |
151 | 5.97k | const int nodes_length = pack_nodes(log, data + offset, length - offset, announce->tcp_relays, |
152 | 5.97k | announce->tcp_relays_count); |
153 | | |
154 | 5.97k | if (nodes_length == -1) { |
155 | 0 | LOGGER_ERROR(log, "Failed to pack TCP nodes"); |
156 | 0 | return -1; |
157 | 0 | } |
158 | | |
159 | 5.97k | return nodes_length + offset; |
160 | 5.97k | } |
161 | | |
162 | | /** |
163 | | * Unpacks `announce` into `data` buffer of size `length`. |
164 | | * |
165 | | * Returns the size of the unpacked data on success. |
166 | | * Returns -1 on failure. |
167 | | */ |
168 | | static int gca_unpack_announce(const Logger *_Nonnull log, const uint8_t *_Nonnull data, uint16_t length, GC_Announce *_Nonnull announce) |
169 | 8.96k | { |
170 | 8.96k | if (length < ENC_PUBLIC_KEY_SIZE + 2) { |
171 | 202 | LOGGER_ERROR(log, "Invalid announce length: %u", length); |
172 | 202 | return -1; |
173 | 202 | } |
174 | | |
175 | 8.76k | if (data == nullptr) { |
176 | 0 | LOGGER_ERROR(log, "data is null"); |
177 | 0 | return -1; |
178 | 0 | } |
179 | | |
180 | 8.76k | if (announce == nullptr) { |
181 | 0 | LOGGER_ERROR(log, "announce is null"); |
182 | 0 | return -1; |
183 | 0 | } |
184 | | |
185 | 8.76k | uint16_t offset = 0; |
186 | 8.76k | memcpy(announce->peer_public_key, data + offset, ENC_PUBLIC_KEY_SIZE); |
187 | 8.76k | offset += ENC_PUBLIC_KEY_SIZE; |
188 | | |
189 | 8.76k | net_unpack_bool(&data[offset], &announce->ip_port_is_set); |
190 | 8.76k | ++offset; |
191 | | |
192 | 8.76k | announce->tcp_relays_count = data[offset]; |
193 | 8.76k | ++offset; |
194 | | |
195 | 8.76k | if (announce->tcp_relays_count > GCA_MAX_ANNOUNCED_TCP_RELAYS) { |
196 | 66 | return -1; |
197 | 66 | } |
198 | | |
199 | 8.69k | if (announce->ip_port_is_set) { |
200 | 7.32k | if (length - offset == 0) { |
201 | 195 | return -1; |
202 | 195 | } |
203 | | |
204 | 7.12k | const int ip_port_length = unpack_ip_port(&announce->ip_port, data + offset, length - offset, false); |
205 | | |
206 | 7.12k | if (ip_port_length == -1) { |
207 | 263 | LOGGER_ERROR(log, "Failed to unpack ip_port"); |
208 | 263 | return -1; |
209 | 263 | } |
210 | | |
211 | 6.86k | offset += ip_port_length; |
212 | 6.86k | } |
213 | | |
214 | 8.23k | uint16_t nodes_length; |
215 | 8.23k | const int nodes_count = unpack_nodes(announce->tcp_relays, announce->tcp_relays_count, &nodes_length, |
216 | 8.23k | data + offset, length - offset, true); |
217 | | |
218 | 8.23k | if (nodes_count != announce->tcp_relays_count) { |
219 | 218 | LOGGER_ERROR(log, "Failed to unpack TCP nodes"); |
220 | 218 | return -1; |
221 | 218 | } |
222 | | |
223 | 8.01k | return offset + nodes_length; |
224 | 8.23k | } |
225 | | |
226 | | int gca_pack_public_announce(const Logger *log, uint8_t *data, uint16_t length, |
227 | | const GC_Public_Announce *public_announce) |
228 | 373 | { |
229 | 373 | if (public_announce == nullptr || data == nullptr || length < CHAT_ID_SIZE) { |
230 | 1 | return -1; |
231 | 1 | } |
232 | | |
233 | 372 | memcpy(data, public_announce->chat_public_key, CHAT_ID_SIZE); |
234 | | |
235 | 372 | const int packed_size = gca_pack_announce(log, data + CHAT_ID_SIZE, length - CHAT_ID_SIZE, |
236 | 372 | &public_announce->base_announce); |
237 | | |
238 | 372 | if (packed_size < 0) { |
239 | 4 | LOGGER_ERROR(log, "Failed to pack public group announce"); |
240 | 4 | return -1; |
241 | 4 | } |
242 | | |
243 | 368 | return packed_size + CHAT_ID_SIZE; |
244 | 372 | } |
245 | | |
246 | | int gca_unpack_public_announce(const Logger *log, const uint8_t *data, uint16_t length, |
247 | | GC_Public_Announce *public_announce) |
248 | 3.29k | { |
249 | 3.29k | if (length < CHAT_ID_SIZE) { |
250 | 211 | LOGGER_ERROR(log, "invalid public announce length: %u", length); |
251 | 211 | return -1; |
252 | 211 | } |
253 | | |
254 | 3.08k | if (data == nullptr) { |
255 | 0 | LOGGER_ERROR(log, "data is null"); |
256 | 0 | return -1; |
257 | 0 | } |
258 | | |
259 | 3.08k | if (public_announce == nullptr) { |
260 | 0 | LOGGER_ERROR(log, "public_announce is null"); |
261 | 0 | return -1; |
262 | 0 | } |
263 | | |
264 | 3.08k | memcpy(public_announce->chat_public_key, data, CHAT_ID_SIZE); |
265 | | |
266 | 3.08k | const int base_announce_size = gca_unpack_announce(log, data + ENC_PUBLIC_KEY_SIZE, length - ENC_PUBLIC_KEY_SIZE, |
267 | 3.08k | &public_announce->base_announce); |
268 | | |
269 | 3.08k | if (base_announce_size == -1) { |
270 | 907 | LOGGER_ERROR(log, "Failed to unpack group announce"); |
271 | 907 | return -1; |
272 | 907 | } |
273 | | |
274 | 2.18k | return base_announce_size + CHAT_ID_SIZE; |
275 | 3.08k | } |
276 | | |
277 | | int gca_pack_announces_list(const Logger *log, uint8_t *data, uint16_t length, const GC_Announce *announces, |
278 | | uint8_t announces_count, size_t *processed) |
279 | 1.55k | { |
280 | 1.55k | if (data == nullptr) { |
281 | 0 | LOGGER_ERROR(log, "data is null"); |
282 | 0 | return -1; |
283 | 0 | } |
284 | | |
285 | 1.55k | if (announces == nullptr) { |
286 | 0 | LOGGER_ERROR(log, "announces is null"); |
287 | 0 | return -1; |
288 | 0 | } |
289 | | |
290 | 1.55k | uint16_t offset = 0; |
291 | | |
292 | 7.04k | for (size_t i = 0; i < announces_count; ++i) { |
293 | 5.55k | const int packed_length = gca_pack_announce(log, data + offset, length - offset, &announces[i]); |
294 | | |
295 | 5.55k | if (packed_length < 0) { |
296 | 60 | LOGGER_ERROR(log, "Failed to pack group announce"); |
297 | 60 | return -1; |
298 | 60 | } |
299 | | |
300 | 5.49k | offset += packed_length; |
301 | 5.49k | } |
302 | | |
303 | 1.49k | if (processed != nullptr) { |
304 | 1.49k | *processed = offset; |
305 | 1.49k | } |
306 | | |
307 | 1.49k | return announces_count; |
308 | 1.55k | } |
309 | | |
310 | | int gca_unpack_announces_list(const Logger *log, const uint8_t *data, uint16_t length, GC_Announce *announces, |
311 | | uint8_t max_count) |
312 | 1.65k | { |
313 | 1.65k | if (data == nullptr) { |
314 | 0 | LOGGER_ERROR(log, "data is null"); |
315 | 0 | return -1; |
316 | 0 | } |
317 | | |
318 | 1.65k | if (announces == nullptr) { |
319 | 0 | LOGGER_ERROR(log, "announces is null"); |
320 | 0 | return -1; |
321 | 0 | } |
322 | | |
323 | 1.65k | uint16_t offset = 0; |
324 | 1.65k | int announces_count = 0; |
325 | | |
326 | 7.49k | for (size_t i = 0; i < max_count && length > offset; ++i) { |
327 | 5.87k | const int unpacked_length = gca_unpack_announce(log, data + offset, length - offset, &announces[i]); |
328 | | |
329 | 5.87k | if (unpacked_length == -1) { |
330 | 37 | LOGGER_WARNING(log, "Failed to unpack group announce: %d %d", length, offset); |
331 | 37 | return -1; |
332 | 37 | } |
333 | | |
334 | 5.83k | offset += unpacked_length; |
335 | 5.83k | ++announces_count; |
336 | 5.83k | } |
337 | | |
338 | 1.61k | return announces_count; |
339 | 1.65k | } |
340 | | |
341 | | static GC_Announces *_Nullable gca_new_announces(const Memory *_Nonnull mem, GC_Announces_List *_Nonnull gc_announces_list, const GC_Public_Announce *_Nonnull public_announce) |
342 | 466 | { |
343 | 466 | GC_Announces *announces = (GC_Announces *)mem_alloc(mem, sizeof(GC_Announces)); |
344 | | |
345 | 466 | if (announces == nullptr) { |
346 | 0 | return nullptr; |
347 | 0 | } |
348 | | |
349 | 466 | announces->index = 0; |
350 | 466 | announces->prev_announce = nullptr; |
351 | | |
352 | 466 | if (gc_announces_list->root_announces != nullptr) { |
353 | 267 | gc_announces_list->root_announces->prev_announce = announces; |
354 | 267 | } |
355 | | |
356 | 466 | announces->next_announce = gc_announces_list->root_announces; |
357 | 466 | gc_announces_list->root_announces = announces; |
358 | 466 | memcpy(announces->chat_id, public_announce->chat_public_key, CHAT_ID_SIZE); |
359 | | |
360 | 466 | return announces; |
361 | 466 | } |
362 | | |
363 | | GC_Peer_Announce *gca_add_announce(const Memory *mem, const Mono_Time *mono_time, GC_Announces_List *gc_announces_list, |
364 | | const GC_Public_Announce *public_announce) |
365 | 2.54k | { |
366 | 2.54k | if (gc_announces_list == nullptr || public_announce == nullptr) { |
367 | 0 | return nullptr; |
368 | 0 | } |
369 | | |
370 | 2.54k | GC_Announces *announces = get_announces_by_chat_id(gc_announces_list, public_announce->chat_public_key); |
371 | | |
372 | | // No entry for this chat_id exists so we create one |
373 | 2.54k | if (announces == nullptr) { |
374 | 466 | announces = gca_new_announces(mem, gc_announces_list, public_announce); |
375 | | |
376 | 466 | if (announces == nullptr) { |
377 | 0 | return nullptr; |
378 | 0 | } |
379 | 466 | } |
380 | | |
381 | 2.54k | const uint64_t cur_time = mono_time_get(mono_time); |
382 | | |
383 | 2.54k | announces->last_announce_received_timestamp = cur_time; |
384 | | |
385 | 2.54k | const uint64_t index = announces->index % GCA_MAX_SAVED_ANNOUNCES_PER_GC; |
386 | | |
387 | 2.54k | GC_Peer_Announce *gc_peer_announce = &announces->peer_announces[index]; |
388 | | |
389 | 2.54k | gc_peer_announce->base_announce = public_announce->base_announce; |
390 | | |
391 | 2.54k | gc_peer_announce->timestamp = cur_time; |
392 | | |
393 | 2.54k | ++announces->index; |
394 | | |
395 | 2.54k | return gc_peer_announce; |
396 | 2.54k | } |
397 | | |
398 | | bool gca_is_valid_announce(const GC_Announce *announce) |
399 | 4.63k | { |
400 | 4.63k | if (announce == nullptr) { |
401 | 0 | return false; |
402 | 0 | } |
403 | | |
404 | 4.63k | return announce->tcp_relays_count > 0 || announce->ip_port_is_set; |
405 | 4.63k | } |
406 | | |
407 | | GC_Announces_List *new_gca_list(const Memory *mem) |
408 | 3.09k | { |
409 | 3.09k | GC_Announces_List *announces_list = (GC_Announces_List *)mem_alloc(mem, sizeof(GC_Announces_List)); |
410 | | |
411 | 3.09k | if (announces_list == nullptr) { |
412 | 17 | return nullptr; |
413 | 17 | } |
414 | | |
415 | 3.08k | announces_list->mem = mem; |
416 | | |
417 | 3.08k | return announces_list; |
418 | 3.09k | } |
419 | | |
420 | | void kill_gca(GC_Announces_List *announces_list) |
421 | 2.20k | { |
422 | 2.20k | if (announces_list == nullptr) { |
423 | 1 | return; |
424 | 1 | } |
425 | | |
426 | 2.20k | GC_Announces *root = announces_list->root_announces; |
427 | | |
428 | 2.46k | while (root != nullptr) { |
429 | 256 | GC_Announces *next = root->next_announce; |
430 | 256 | mem_delete(announces_list->mem, root); |
431 | 256 | root = next; |
432 | 256 | } |
433 | | |
434 | 2.20k | mem_delete(announces_list->mem, announces_list); |
435 | 2.20k | } |
436 | | |
437 | | /* How long we save a peer's announce before we consider it stale and remove it. */ |
438 | 2.15k | #define GCA_ANNOUNCE_SAVE_TIMEOUT 30 |
439 | | |
440 | | /* How often we run do_gca() */ |
441 | 129k | #define GCA_DO_GCA_TIMEOUT 1 |
442 | | |
443 | | void do_gca(const Mono_Time *mono_time, GC_Announces_List *gc_announces_list) |
444 | 129k | { |
445 | 129k | if (gc_announces_list == nullptr) { |
446 | 0 | return; |
447 | 0 | } |
448 | | |
449 | 129k | if (!mono_time_is_timeout(mono_time, gc_announces_list->last_timeout_check, GCA_DO_GCA_TIMEOUT)) { |
450 | 109k | return; |
451 | 109k | } |
452 | | |
453 | 19.5k | gc_announces_list->last_timeout_check = mono_time_get(mono_time); |
454 | | |
455 | 19.5k | GC_Announces *announces = gc_announces_list->root_announces; |
456 | | |
457 | 21.7k | while (announces != nullptr) { |
458 | 2.15k | if (mono_time_is_timeout(mono_time, announces->last_announce_received_timestamp, GCA_ANNOUNCE_SAVE_TIMEOUT)) { |
459 | 2 | GC_Announces *to_delete = announces; |
460 | 2 | announces = announces->next_announce; |
461 | 2 | remove_announces(gc_announces_list, to_delete); |
462 | 2 | continue; |
463 | 2 | } |
464 | | |
465 | 2.15k | announces = announces->next_announce; |
466 | 2.15k | } |
467 | 19.5k | } |
468 | | |
469 | | void cleanup_gca(GC_Announces_List *gc_announces_list, const uint8_t *chat_id) |
470 | 702 | { |
471 | 702 | if (gc_announces_list == nullptr || chat_id == nullptr) { |
472 | 0 | return; |
473 | 0 | } |
474 | | |
475 | 702 | GC_Announces *announces = get_announces_by_chat_id(gc_announces_list, chat_id); |
476 | | |
477 | 702 | if (announces != nullptr) { |
478 | 208 | remove_announces(gc_announces_list, announces); |
479 | 208 | } |
480 | 702 | } |