/work/toxcore/group_common.h
Line | Count | Source |
1 | | /* SPDX-License-Identifier: GPL-3.0-or-later |
2 | | * Copyright © 2016-2026 The TokTok team. |
3 | | */ |
4 | | |
5 | | /** |
6 | | * Common groupchat data structures. |
7 | | */ |
8 | | |
9 | | #ifndef C_TOXCORE_TOXCORE_GROUP_COMMON_H |
10 | | #define C_TOXCORE_TOXCORE_GROUP_COMMON_H |
11 | | |
12 | | #include <stdbool.h> |
13 | | #include <stdint.h> |
14 | | |
15 | | #include "DHT.h" |
16 | | #include "TCP_connection.h" |
17 | | #include "attributes.h" |
18 | | #include "crypto_core.h" |
19 | | #include "group_moderation.h" |
20 | | #include "logger.h" |
21 | | #include "mem.h" |
22 | | #include "mono_time.h" |
23 | | #include "net.h" |
24 | | #include "net_profile.h" |
25 | | #include "network.h" |
26 | | |
27 | | #ifdef __cplusplus |
28 | | extern "C" { |
29 | | #endif |
30 | | |
31 | 7.41k | #define MAX_GC_PART_MESSAGE_SIZE 128 |
32 | 1.46k | #define MAX_GC_NICK_SIZE 128 |
33 | 111 | #define MAX_GC_TOPIC_SIZE 512 |
34 | 1.70k | #define MAX_GC_GROUP_NAME_SIZE 48 |
35 | 682 | #define GC_MESSAGE_PSEUDO_ID_SIZE 4 |
36 | 236 | #define GROUP_MAX_MESSAGE_LENGTH 1372 |
37 | | |
38 | | /* Max size of a packet chunk. Packets larger than this must be split up. |
39 | | * |
40 | | * For an explanation on why this value was chosen, see the following link: https://archive.ph/vsCOG |
41 | | */ |
42 | 9.70k | #define MAX_GC_PACKET_CHUNK_SIZE 500 |
43 | | /* Max size of an incoming packet chunk that is allowed */ |
44 | 2.30k | #define MAX_GC_PACKET_INCOMING_CHUNK_SIZE 1372 |
45 | | |
46 | 234 | #define MAX_GC_MESSAGE_SIZE GROUP_MAX_MESSAGE_LENGTH |
47 | 230 | #define MAX_GC_MESSAGE_RAW_SIZE (MAX_GC_MESSAGE_SIZE + GC_MESSAGE_PSEUDO_ID_SIZE) |
48 | 4 | #define MAX_GC_CUSTOM_LOSSLESS_PACKET_SIZE 1373 |
49 | 1.09k | #define MAX_GC_CUSTOM_LOSSY_PACKET_SIZE 1373 |
50 | 1.95k | #define MAX_GC_PASSWORD_SIZE 32 |
51 | 6 | #define MAX_GC_SAVED_INVITES 10 |
52 | 36 | #define MAX_GC_PEERS_DEFAULT 100 |
53 | 195 | #define MAX_GC_SAVED_TIMEOUTS 12 |
54 | 34.4k | #define GC_MAX_SAVED_PEERS 100 |
55 | 646 | #define GC_SAVED_PEER_SIZE (ENC_PUBLIC_KEY_SIZE + sizeof(Node_format) + sizeof(IP_Port)) |
56 | | |
57 | | /* Max size of a complete encrypted packet including headers. */ |
58 | 7.45k | #define MAX_GC_PACKET_SIZE (MAX_GC_PACKET_CHUNK_SIZE * 100) |
59 | | |
60 | | /* Max number of messages to store in the send/recv arrays */ |
61 | 786k | #define GCC_BUFFER_SIZE 2048 |
62 | | |
63 | | /** Self UDP status. Must correspond to return values from `ipport_self_copy()`. */ |
64 | | typedef enum Self_UDP_Status { |
65 | | SELF_UDP_STATUS_NONE = 0x00, |
66 | | SELF_UDP_STATUS_WAN = 0x01, |
67 | | SELF_UDP_STATUS_LAN = 0x02, |
68 | | } Self_UDP_Status; |
69 | | |
70 | | /** Group exit types. */ |
71 | | typedef enum Group_Exit_Type { |
72 | | GC_EXIT_TYPE_QUIT = 0x00, // Peer left the group |
73 | | GC_EXIT_TYPE_TIMEOUT = 0x01, // Peer connection timed out |
74 | | GC_EXIT_TYPE_DISCONNECTED = 0x02, // Peer diconnected from group |
75 | | GC_EXIT_TYPE_SELF_DISCONNECTED = 0x03, // Self disconnected from group |
76 | | GC_EXIT_TYPE_KICKED = 0x04, // Peer was kicked from the group |
77 | | GC_EXIT_TYPE_SYNC_ERR = 0x05, // Peer failed to sync with the group |
78 | | GC_EXIT_TYPE_NO_CALLBACK = 0x06, // The peer exit callback should not be triggered |
79 | | } Group_Exit_Type; |
80 | | |
81 | | typedef struct GC_Exit_Info { |
82 | | uint8_t part_message[MAX_GC_PART_MESSAGE_SIZE]; |
83 | | uint16_t length; |
84 | | Group_Exit_Type exit_type; |
85 | | } GC_Exit_Info; |
86 | | |
87 | | typedef struct GC_PeerAddress { |
88 | | Extended_Public_Key public_key; |
89 | | IP_Port ip_port; |
90 | | } GC_PeerAddress; |
91 | | |
92 | | typedef struct GC_Message_Array_Entry { |
93 | | uint8_t *_Nullable data; |
94 | | uint16_t data_length; |
95 | | uint8_t packet_type; |
96 | | uint64_t message_id; |
97 | | uint64_t time_added; |
98 | | uint64_t last_send_try; |
99 | | } GC_Message_Array_Entry; |
100 | | |
101 | | typedef struct GC_Connection { |
102 | | uint64_t send_message_id; /* message_id of the next message we send to peer */ |
103 | | |
104 | | uint16_t send_array_start; /* send_array index of oldest item */ |
105 | | GC_Message_Array_Entry *_Nullable send_array; |
106 | | |
107 | | uint64_t received_message_id; /* message_id of peer's last message to us */ |
108 | | GC_Message_Array_Entry *_Nullable recv_array; |
109 | | |
110 | | uint64_t last_chunk_id; /* The message ID of the last packet fragment we received */ |
111 | | |
112 | | GC_PeerAddress addr; /* holds peer's extended real public key and ip_port */ |
113 | | uint32_t public_key_hash; /* Jenkins one at a time hash of peer's real encryption public key */ |
114 | | |
115 | | uint8_t session_public_key[ENC_PUBLIC_KEY_SIZE]; /* self session public key for this peer */ |
116 | | uint8_t session_secret_key[ENC_SECRET_KEY_SIZE]; /* self session secret key for this peer */ |
117 | | uint8_t session_shared_key[CRYPTO_SHARED_KEY_SIZE]; /* made with our session sk and peer's session pk */ |
118 | | |
119 | | int tcp_connection_num; |
120 | | int32_t friend_number; /* The messenger friend number associated with this group connection. Used to discover the peer's IP/port if it wasn't available during the initial invite. */ |
121 | | uint64_t last_sent_tcp_relays_time; /* the last time we attempted to send this peer our tcp relays */ |
122 | | uint16_t tcp_relay_share_index; |
123 | | uint64_t last_received_direct_time; /* the last time we received a direct UDP packet from this connection */ |
124 | | uint64_t last_sent_direct_try_time; /* the last time we tried sending a direct UDP packet */ |
125 | | uint64_t last_sent_ip_time; /* the last time we sent our ip info to this peer in a ping packet */ |
126 | | |
127 | | Node_format connected_tcp_relays[MAX_FRIEND_TCP_CONNECTIONS]; |
128 | | uint16_t tcp_relays_count; |
129 | | |
130 | | uint64_t last_received_packet_time; /* The last time we successfully processed any packet from this peer */ |
131 | | uint64_t last_requested_packet_time; /* The last time we requested a missing packet from this peer */ |
132 | | uint64_t last_sent_ping_time; |
133 | | uint64_t last_sync_response; /* the last time we sent this peer a sync response */ |
134 | | uint8_t oob_relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; |
135 | | bool self_is_closer; /* true if we're "closer" to the chat_id than this peer (uses real pk's) */ |
136 | | |
137 | | bool confirmed; /* true if this peer has given us their info */ |
138 | | bool handshaked; /* true if we've successfully handshaked with this peer */ |
139 | | uint16_t handshake_attempts; |
140 | | uint64_t last_handshake_request; |
141 | | uint64_t last_handshake_response; |
142 | | uint8_t pending_handshake_type; |
143 | | bool is_pending_handshake_response; |
144 | | bool is_oob_handshake; |
145 | | |
146 | | uint64_t last_key_rotation; /* the last time we rotated session keys for this peer */ |
147 | | bool pending_key_rotation_request; |
148 | | |
149 | | bool pending_delete; /* true if this peer has been marked for deletion */ |
150 | | bool delete_this_iteration; /* true if this peer should be deleted this do_gc() iteration*/ |
151 | | GC_Exit_Info exit_info; |
152 | | } GC_Connection; |
153 | | |
154 | | /*** |
155 | | * Group roles. Roles are hierarchical in that each role has a set of privileges plus |
156 | | * all the privileges of the roles below it. |
157 | | */ |
158 | | typedef enum Group_Role { |
159 | | /** Group creator. All-powerful. Cannot be demoted or kicked. */ |
160 | | GR_FOUNDER = 0x00, |
161 | | |
162 | | /** |
163 | | * May promote or demote peers below them to any role below them. |
164 | | * May also kick peers below them and set the topic. |
165 | | */ |
166 | | GR_MODERATOR = 0x01, |
167 | | |
168 | | /** may interact normally with the group. */ |
169 | | GR_USER = 0x02, |
170 | | |
171 | | /** May not interact with the group but may observe. */ |
172 | | GR_OBSERVER = 0x03, |
173 | | } Group_Role; |
174 | | |
175 | | typedef enum Group_Peer_Status { |
176 | | GS_NONE = 0x00, |
177 | | GS_AWAY = 0x01, |
178 | | GS_BUSY = 0x02, |
179 | | } Group_Peer_Status; |
180 | | |
181 | | /** |
182 | | * Group voice states. The state determines which Group Roles have permission to speak. |
183 | | */ |
184 | | typedef enum Group_Voice_State { |
185 | | /** Every group role except Observers may speak. */ |
186 | | GV_ALL = 0x00, |
187 | | |
188 | | /** Only Moderators and the Founder may speak. */ |
189 | | GV_MODS = 0x01, |
190 | | |
191 | | /** Only the Founder may speak. */ |
192 | | GV_FOUNDER = 0x02, |
193 | | } Group_Voice_State; |
194 | | |
195 | | /** Group connection states. */ |
196 | | typedef enum GC_Conn_State { |
197 | | CS_NONE = 0x00, // Indicates a group is not initialized |
198 | | CS_DISCONNECTED = 0x01, // Not receiving or sending any packets |
199 | | CS_CONNECTING = 0x02, // Attempting to establish a connection with peers in the group |
200 | | CS_CONNECTED = 0x03, // Has successfully received a sync response from a peer in the group |
201 | | } GC_Conn_State; |
202 | | |
203 | | /** Group privacy states. */ |
204 | | typedef enum Group_Privacy_State { |
205 | | GI_PUBLIC = 0x00, // Anyone with the chat ID may join the group |
206 | | GI_PRIVATE = 0x01, // Peers may only join the group via a friend invite |
207 | | } Group_Privacy_State; |
208 | | |
209 | | /** Handshake join types. */ |
210 | | typedef enum Group_Handshake_Join_Type { |
211 | | HJ_PUBLIC = 0x00, // Indicates the group was joined via the DHT |
212 | | HJ_PRIVATE = 0x01, // Indicates the group was joined via private friend invite |
213 | | } Group_Handshake_Join_Type; |
214 | | |
215 | | typedef struct GC_SavedPeerInfo { |
216 | | uint8_t public_key[ENC_PUBLIC_KEY_SIZE]; |
217 | | Node_format tcp_relay; |
218 | | IP_Port ip_port; |
219 | | } GC_SavedPeerInfo; |
220 | | |
221 | | /** Holds info about peers who recently timed out */ |
222 | | typedef struct GC_TimedOutPeer { |
223 | | GC_SavedPeerInfo addr; |
224 | | uint64_t last_seen; // the time the peer disconnected |
225 | | uint64_t last_reconn_try; // the last time we tried to establish a new connection |
226 | | } GC_TimedOutPeer; |
227 | | |
228 | | typedef bitwise uint32_t GC_Peer_Id_Value; |
229 | | |
230 | | typedef struct GC_Peer_Id { |
231 | | GC_Peer_Id_Value value; |
232 | | } GC_Peer_Id; |
233 | | |
234 | | GC_Peer_Id gc_peer_id_from_int(uint32_t value); |
235 | | uint32_t gc_peer_id_to_int(GC_Peer_Id peer_id); |
236 | | |
237 | | typedef struct GC_Peer { |
238 | | /* Below state is sent to other peers in peer info exchange */ |
239 | | uint8_t nick[MAX_GC_NICK_SIZE]; |
240 | | uint16_t nick_length; |
241 | | uint8_t status; |
242 | | |
243 | | /* Below state is local only */ |
244 | | Group_Role role; |
245 | | GC_Peer_Id peer_id; // permanent ID (used for the public API) |
246 | | bool ignore; |
247 | | |
248 | | GC_Connection gconn; |
249 | | } GC_Peer; |
250 | | |
251 | | typedef struct GC_SharedState { |
252 | | uint32_t version; |
253 | | Extended_Public_Key founder_public_key; |
254 | | uint16_t maxpeers; |
255 | | uint16_t group_name_len; |
256 | | uint8_t group_name[MAX_GC_GROUP_NAME_SIZE]; |
257 | | Group_Privacy_State privacy_state; // GI_PUBLIC (uses DHT) or GI_PRIVATE (invite only) |
258 | | uint16_t password_length; |
259 | | uint8_t password[MAX_GC_PASSWORD_SIZE]; |
260 | | uint8_t mod_list_hash[MOD_MODERATION_HASH_SIZE]; |
261 | | uint32_t topic_lock; // equal to GC_TOPIC_LOCK_ENABLED when lock is enabled |
262 | | Group_Voice_State voice_state; |
263 | | } GC_SharedState; |
264 | | |
265 | | typedef struct GC_TopicInfo { |
266 | | uint32_t version; |
267 | | uint16_t length; |
268 | | uint16_t checksum; // used for syncing problems. the checksum with the highest value gets priority. |
269 | | uint8_t topic[MAX_GC_TOPIC_SIZE]; |
270 | | uint8_t public_sig_key[SIG_PUBLIC_KEY_SIZE]; // Public signature key of the topic setter |
271 | | } GC_TopicInfo; |
272 | | |
273 | | typedef struct GC_Chat { |
274 | | Mono_Time *_Nonnull mono_time; |
275 | | const Logger *_Nonnull log; |
276 | | const Memory *_Nonnull mem; |
277 | | const Random *_Nonnull rng; |
278 | | |
279 | | uint32_t connected_tcp_relays; |
280 | | Self_UDP_Status self_udp_status; |
281 | | IP_Port self_ip_port; |
282 | | |
283 | | Networking_Core *_Nonnull net; |
284 | | TCP_Connections *_Nonnull tcp_conn; |
285 | | |
286 | | uint64_t last_checked_tcp_relays; |
287 | | Group_Handshake_Join_Type join_type; |
288 | | |
289 | | GC_Peer *_Nullable group; |
290 | | Moderation moderation; |
291 | | |
292 | | GC_Conn_State connection_state; |
293 | | |
294 | | GC_SharedState shared_state; |
295 | | uint8_t shared_state_sig[SIGNATURE_SIZE]; // signed by founder using the chat secret key |
296 | | |
297 | | GC_TopicInfo topic_info; |
298 | | uint8_t topic_sig[SIGNATURE_SIZE]; // signed by the peer who set the current topic |
299 | | uint16_t topic_prev_checksum; // checksum of the previous topic |
300 | | uint64_t topic_time_set; |
301 | | |
302 | | uint16_t peers_checksum; // sum of the public key hash of every confirmed peer in the group |
303 | | uint16_t roles_checksum; // sum of every confirmed peer's role plus the first byte of their public key |
304 | | |
305 | | uint32_t numpeers; |
306 | | int group_number; |
307 | | |
308 | | Extended_Public_Key chat_public_key; // the chat_id is the sig portion |
309 | | Extended_Secret_Key chat_secret_key; // only used by the founder |
310 | | |
311 | | Extended_Public_Key self_public_key; |
312 | | Extended_Secret_Key self_secret_key; |
313 | | |
314 | | uint64_t time_connected; |
315 | | uint64_t last_ping_interval; |
316 | | uint64_t last_sync_request; // The last time we sent a sync request to any peer |
317 | | uint64_t last_sync_response_peer_list; // The last time we sent the peer list to any peer |
318 | | uint64_t last_time_peers_loaded; |
319 | | |
320 | | /* keeps track of frequency of new inbound connections */ |
321 | | uint8_t connection_o_metre; |
322 | | uint64_t connection_cooldown_timer; |
323 | | bool block_handshakes; |
324 | | |
325 | | int32_t saved_invites[MAX_GC_SAVED_INVITES]; |
326 | | uint8_t saved_invites_index; |
327 | | |
328 | | /** A list of recently seen peers in case we disconnect from a private group. |
329 | | * Peers are added once they're confirmed, and only if there are vacant |
330 | | * spots (older connections get priority). An entry is removed only when the list |
331 | | * is full, its respective peer goes offline, and an online peer who isn't yet |
332 | | * present in the list can be added. |
333 | | */ |
334 | | GC_SavedPeerInfo saved_peers[GC_MAX_SAVED_PEERS]; |
335 | | |
336 | | GC_TimedOutPeer timeout_list[MAX_GC_SAVED_TIMEOUTS]; |
337 | | size_t timeout_list_index; |
338 | | uint64_t last_timed_out_reconn_try; // the last time we tried to reconnect to timed out peers |
339 | | |
340 | | bool update_self_announces; // true if we should try to update our announcements |
341 | | uint64_t last_self_announce_check; // the last time we checked if we should update our announcements |
342 | | uint64_t last_time_self_announce; // the last time we announced the group |
343 | | uint8_t announced_tcp_relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; // The pk of the last TCP relay we announced |
344 | | |
345 | | uint8_t m_group_public_key[CRYPTO_PUBLIC_KEY_SIZE]; // public key for group's messenger friend connection |
346 | | int friend_connection_id; // identifier for group's messenger friend connection |
347 | | |
348 | | bool flag_exit; // true if the group will be deleted after the next do_gc() iteration |
349 | | } GC_Chat; |
350 | | |
351 | | #ifndef MESSENGER_DEFINED |
352 | | #define MESSENGER_DEFINED |
353 | | typedef struct Messenger Messenger; |
354 | | #endif /* MESSENGER_DEFINED */ |
355 | | |
356 | | typedef void gc_message_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, unsigned int type, |
357 | | const uint8_t *_Nonnull message, size_t length, uint32_t message_id, void *_Nullable user_data); |
358 | | typedef void gc_private_message_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, unsigned int type, |
359 | | const uint8_t *_Nonnull message, size_t length, uint32_t message_id, void *_Nullable user_data); |
360 | | typedef void gc_custom_packet_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, const uint8_t *_Nonnull data, |
361 | | size_t length, void *_Nullable user_data); |
362 | | typedef void gc_custom_private_packet_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, |
363 | | const uint8_t *_Nonnull data, size_t length, void *_Nullable user_data); |
364 | | typedef void gc_moderation_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id source_peer_number, |
365 | | GC_Peer_Id target_peer_number, unsigned int mod_type, void *_Nullable user_data); |
366 | | typedef void gc_nick_change_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, const uint8_t *_Nonnull name, |
367 | | size_t length, void *_Nullable user_data); |
368 | | typedef void gc_status_change_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, unsigned int status, |
369 | | void *_Nullable user_data); |
370 | | typedef void gc_topic_change_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, const uint8_t *_Nullable topic, |
371 | | size_t length, void *_Nullable user_data); |
372 | | typedef void gc_topic_lock_cb(const Messenger *_Nonnull m, uint32_t group_number, unsigned int topic_lock, void *_Nullable user_data); |
373 | | typedef void gc_voice_state_cb(const Messenger *_Nonnull m, uint32_t group_number, unsigned int voice_state, void *_Nullable user_data); |
374 | | typedef void gc_peer_limit_cb(const Messenger *_Nonnull m, uint32_t group_number, uint32_t peer_limit, void *_Nullable user_data); |
375 | | typedef void gc_privacy_state_cb(const Messenger *_Nonnull m, uint32_t group_number, unsigned int privacy_state, void *_Nullable user_data); |
376 | | typedef void gc_password_cb(const Messenger *_Nonnull m, uint32_t group_number, const uint8_t *_Nullable password, size_t length, |
377 | | void *_Nullable user_data); |
378 | | typedef void gc_peer_join_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, void *_Nullable user_data); |
379 | | typedef void gc_peer_exit_cb(const Messenger *_Nonnull m, uint32_t group_number, GC_Peer_Id peer_id, unsigned int exit_type, |
380 | | const uint8_t *_Nullable name, size_t name_length, const uint8_t *_Nullable part_message, size_t length, |
381 | | void *_Nullable user_data); |
382 | | typedef void gc_self_join_cb(const Messenger *_Nonnull m, uint32_t group_number, void *_Nullable user_data); |
383 | | typedef void gc_rejected_cb(const Messenger *_Nonnull m, uint32_t group_number, unsigned int fail_type, void *_Nullable user_data); |
384 | | |
385 | | typedef struct GC_Session { |
386 | | Messenger *_Nonnull messenger; |
387 | | GC_Chat *_Nullable chats; |
388 | | Net_Profile *_Nonnull tcp_np; |
389 | | struct GC_Announces_List *_Nonnull announces_list; |
390 | | |
391 | | uint32_t chats_index; |
392 | | |
393 | | gc_message_cb *_Nullable message; |
394 | | gc_private_message_cb *_Nullable private_message; |
395 | | gc_custom_packet_cb *_Nullable custom_packet; |
396 | | gc_custom_private_packet_cb *_Nullable custom_private_packet; |
397 | | gc_moderation_cb *_Nullable moderation; |
398 | | gc_nick_change_cb *_Nullable nick_change; |
399 | | gc_status_change_cb *_Nullable status_change; |
400 | | gc_topic_change_cb *_Nullable topic_change; |
401 | | gc_topic_lock_cb *_Nullable topic_lock; |
402 | | gc_voice_state_cb *_Nullable voice_state; |
403 | | gc_peer_limit_cb *_Nullable peer_limit; |
404 | | gc_privacy_state_cb *_Nullable privacy_state; |
405 | | gc_password_cb *_Nullable password; |
406 | | gc_peer_join_cb *_Nullable peer_join; |
407 | | gc_peer_exit_cb *_Nullable peer_exit; |
408 | | gc_self_join_cb *_Nullable self_join; |
409 | | gc_rejected_cb *_Nullable rejected; |
410 | | } GC_Session; |
411 | | |
412 | | /** @brief Adds a new peer to group_number's peer list. |
413 | | * |
414 | | * Return peer_number on success. |
415 | | * Return -1 on failure. |
416 | | * Return -2 if a peer with public_key is already in our peerlist. |
417 | | */ |
418 | | int peer_add(GC_Chat *_Nonnull chat, const IP_Port *_Nullable ipp, const uint8_t *_Nonnull public_key); |
419 | | /** @brief Unpacks saved peers from `data` of size `length` into `chat`. |
420 | | * |
421 | | * Returns the number of unpacked peers on success. |
422 | | * Returns -1 on failure. |
423 | | */ |
424 | | int unpack_gc_saved_peers(GC_Chat *_Nonnull chat, const uint8_t *_Nonnull data, uint16_t length); |
425 | | |
426 | | /** @brief Packs all valid entries from saved peerlist into `data`. |
427 | | * |
428 | | * If `processed` is non-null it will be set to the length of the packed data |
429 | | * on success, and will be untouched on error. |
430 | | * |
431 | | * Return the number of packed saved peers on success. |
432 | | * Return -1 if buffer is too small. |
433 | | */ |
434 | | int pack_gc_saved_peers(const GC_Chat *_Nonnull chat, uint8_t *_Nonnull data, uint16_t length, uint16_t *_Nullable processed); |
435 | | |
436 | | #ifdef __cplusplus |
437 | | } /* extern "C" */ |
438 | | #endif |
439 | | |
440 | | #endif /* C_TOXCORE_TOXCORE_GROUP_COMMON_H */ |