summaryrefslogtreecommitdiff
path: root/toxcore/group.c
diff options
context:
space:
mode:
Diffstat (limited to 'toxcore/group.c')
-rw-r--r--toxcore/group.c781
1 files changed, 781 insertions, 0 deletions
diff --git a/toxcore/group.c b/toxcore/group.c
new file mode 100644
index 00000000..d5244f65
--- /dev/null
+++ b/toxcore/group.c
@@ -0,0 +1,781 @@
1/* group.c
2 *
3 * Slightly better groupchats implementation.
4 *
5 * Copyright (C) 2014 Tox project All Rights Reserved.
6 *
7 * This file is part of Tox.
8 *
9 * Tox is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * Tox is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with Tox. If not, see <http://www.gnu.org/licenses/>.
21 *
22 */
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27
28#include "group.h"
29#include "util.h"
30
31/* return 1 if the groupnumber is not valid.
32 * return 0 if the groupnumber is valid.
33 */
34static uint8_t groupnumber_not_valid(const Group_Chats *g_c, int groupnumber)
35{
36 if ((unsigned int)groupnumber >= g_c->num_chats)
37 return 1;
38
39 if (g_c->chats == NULL)
40 return 1;
41
42 if (g_c->chats[groupnumber].status == GROUPCHAT_STATUS_NONE)
43 return 1;
44
45 return 0;
46}
47
48
49/* Set the size of the groupchat list to num.
50 *
51 * return -1 if realloc fails.
52 * return 0 if it succeeds.
53 */
54static int realloc_groupchats(Group_Chats *g_c, uint32_t num)
55{
56 if (num == 0) {
57 free(g_c->chats);
58 g_c->chats = NULL;
59 return 0;
60 }
61
62 Group_c *newgroup_chats = realloc(g_c->chats, num * sizeof(Group_c));
63
64 if (newgroup_chats == NULL)
65 return -1;
66
67 g_c->chats = newgroup_chats;
68 return 0;
69}
70
71
72/* Create a new empty groupchat connection.
73 *
74 * return -1 on failure.
75 * return groupnumber on success.
76 */
77static int create_group_chat(Group_Chats *g_c)
78{
79 uint32_t i;
80
81 for (i = 0; i < g_c->num_chats; ++i) {
82 if (g_c->chats[i].status == GROUPCHAT_STATUS_NONE)
83 return i;
84 }
85
86 int id = -1;
87
88 if (realloc_groupchats(g_c, g_c->num_chats + 1) == 0) {
89 id = g_c->num_chats;
90 ++g_c->num_chats;
91 memset(&(g_c->chats[id]), 0, sizeof(Group_c));
92 }
93
94 return id;
95}
96
97
98/* Wipe a groupchat.
99 *
100 * return -1 on failure.
101 * return 0 on success.
102 */
103static int wipe_group_chat(Group_Chats *g_c, int groupnumber)
104{
105 if (groupnumber_not_valid(g_c, groupnumber))
106 return -1;
107
108 uint32_t i;
109 memset(&(g_c->chats[groupnumber]), 0 , sizeof(Group_c));
110
111 for (i = g_c->num_chats; i != 0; --i) {
112 if (g_c->chats[i - 1].status != GROUPCHAT_STATUS_NONE)
113 break;
114 }
115
116 if (g_c->num_chats != i) {
117 g_c->num_chats = i;
118 realloc_groupchats(g_c, g_c->num_chats);
119 }
120
121 return 0;
122}
123
124static Group_c *get_group_c(const Group_Chats *g_c, int groupnumber)
125{
126 if (groupnumber_not_valid(g_c, groupnumber))
127 return 0;
128
129 return &g_c->chats[groupnumber];
130}
131
132/*
133 * check if peer with client_id is in peer array.
134 *
135 * return peer index if peer is in chat.
136 * return -1 if peer is not in chat.
137 *
138 * TODO: make this more efficient.
139 */
140
141static int peer_in_chat(const Group_c *chat, const uint8_t *client_id)
142{
143 uint32_t i;
144
145 for (i = 0; i < chat->numpeers; ++i)
146 if (id_equal(chat->group[i].client_id, client_id))
147 return i;
148
149 return -1;
150}
151
152/*
153 * check if group with identifier is in group array.
154 *
155 * return group number if peer is in list.
156 * return -1 if group is not in list.
157 *
158 * TODO: make this more efficient and maybe use constant time comparisons?
159 */
160static int get_group_num(const Group_Chats *g_c, const uint8_t *identifier)
161{
162 uint32_t i;
163
164 for (i = 0; i < g_c->num_chats; ++i)
165 if (memcmp(g_c->chats[i].identifier, identifier, GROUP_IDENTIFIER_LENGTH) == 0)
166 return i;
167
168 return -1;
169}
170
171/*
172 * check if peer with peer_number is in peer array.
173 *
174 * return peer number if peer is in chat.
175 * return -1 if peer is not in chat.
176 *
177 * TODO: make this more efficient.
178 */
179int get_peer_index(Group_c *g, uint16_t peer_number)
180{
181 uint32_t i;
182
183 for (i = 0; i < g->numpeers; ++i)
184 if (g->group[i].peer_number == peer_number)
185 return i;
186
187 return -1;
188}
189
190/*
191 * Add a peer to the group chat.
192 *
193 * return peer_index if success or peer already in chat.
194 * return -1 if error.
195 */
196static int addpeer(Group_c *chat, const uint8_t *client_id, uint16_t peer_number)
197{
198 //TODO
199 //int peer_index = peer_in_chat(chat, client_id);
200
201 //if (peer_index != -1)
202 // return peer_index;
203
204 Group_Peer *temp;
205 temp = realloc(chat->group, sizeof(Group_Peer) * (chat->numpeers + 1));
206
207 if (temp == NULL)
208 return -1;
209
210 memset(&(temp[chat->numpeers]), 0, sizeof(Group_Peer));
211 chat->group = temp;
212
213 id_copy(chat->group[chat->numpeers].client_id, client_id);
214 chat->group[chat->numpeers].peer_number = peer_number;
215
216 chat->group[chat->numpeers].last_recv = unix_time();
217 chat->group[chat->numpeers].last_recv_msgping = unix_time();
218 ++chat->numpeers;
219
220 //if (chat->peer_namelistchange != NULL)
221 // (*chat->peer_namelistchange)(chat, chat->numpeers - 1, CHAT_CHANGE_PEER_ADD, chat->group_namelistchange_userdata);
222
223 return (chat->numpeers - 1);
224}
225
226static int handle_packet(void *object, int number, uint8_t *data, uint16_t length);
227
228/* Add friend to group chat.
229 *
230 * return 0 on success
231 * return -1 on failure.
232 */
233static int add_friend_to_groupchat(Group_Chats *g_c, int32_t friendnumber, int groupnumber, uint16_t other_groupnum)
234{
235 if (!m_friend_exists(g_c->m, friendnumber))
236 return -1;
237
238 Group_c *g = get_group_c(g_c, groupnumber);
239
240 if (!g)
241 return -1;
242
243 uint16_t i, ind = MAX_GROUP_CONNECTIONS;
244
245 for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
246 if (g->close[i].type == GROUPCHAT_CLOSE_NONE) {
247 ind = i;
248 continue;
249 }
250
251 if (g->close[i].type == GROUPCHAT_CLOSE_CONNECTION && g->close[i].number == (uint32_t)friendnumber) {
252 g->close[i].group_number = other_groupnum; /* update groupnum. */
253 return 0; /* Already in list. */
254 }
255
256 break;
257 }
258
259 if (ind == MAX_GROUP_CONNECTIONS)
260 return -1;
261
262 g->close[ind].type = GROUPCHAT_CLOSE_CONNECTION;
263 g->close[ind].number = friendnumber;
264 g->close[ind].group_number = other_groupnum;
265 int friendcon_id = g_c->m->friendlist[friendnumber].friendcon_id;
266 //TODO
267 friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, 0, &handle_packet, 0, g_c->m,
268 friendnumber);
269
270 return 0;
271}
272
273/* Creates a new groupchat and puts it in the chats array.
274 *
275 * return group number on success.
276 * return -1 on failure.
277 */
278int add_groupchat(Group_Chats *g_c)
279{
280 int groupnumber = create_group_chat(g_c);
281
282 if (groupnumber == -1)
283 return -1;
284
285 Group_c *g = &g_c->chats[groupnumber];
286
287 g->status = GROUPCHAT_STATUS_VALID;
288 new_symmetric_key(g->identifier);
289 g->peer_number = 0; /* Founder is peer 0. */
290 return groupnumber;
291}
292
293/* Delete a groupchat from the chats array.
294 *
295 * return 0 on success.
296 * return -1 if failure.
297 */
298int del_groupchat(Group_Chats *g_c, int groupnumber)
299{
300 Group_c *g = get_group_c(g_c, groupnumber);
301
302 if (!g)
303 return -1;
304
305 free(g->group);
306 return wipe_group_chat(g_c, groupnumber);
307}
308
309/* Send a group message packet.
310 *
311 * return 1 on success
312 * return 0 on failure
313 */
314int send_group_message_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length)
315{
316 if (length >= MAX_CRYPTO_DATA_SIZE)
317 return 0;
318
319 uint8_t packet[1 + length];
320 packet[0] = PACKET_ID_MESSAGE_GROUPCHAT;
321 memcpy(packet + 1, data, length);
322 return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
323 m->friendlist[friendnumber].friendcon_id), packet, sizeof(packet), 0) != -1;
324}
325
326#define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH)
327#define INVITE_ID 0
328
329#define INVITE_RESPONSE_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + GROUP_IDENTIFIER_LENGTH)
330#define INVITE_RESPONSE_ID 1
331
332/* invite friendnumber to groupnumber
333 * return 0 on success
334 * return -1 on failure
335 */
336int invite_friend(Group_Chats *g_c, int32_t friendnumber, int groupnumber)
337{
338 Group_c *g = get_group_c(g_c, groupnumber);
339
340 if (!g)
341 return -1;
342
343 uint8_t invite[INVITE_PACKET_SIZE];
344 invite[0] = INVITE_ID;
345 uint16_t groupchat_num = htons((uint16_t)groupnumber);
346 memcpy(invite + 1, &groupchat_num, sizeof(groupchat_num));
347 memcpy(invite + 1 + sizeof(groupchat_num), g->identifier, GROUP_IDENTIFIER_LENGTH);
348
349 if (send_group_invite_packet(g_c->m, friendnumber, invite, sizeof(invite))) {
350 return 0;
351 } else {
352 wipe_group_chat(g_c, groupnumber);
353 return -1;
354 }
355}
356
357/* Join a group (you need to have been invited first.)
358 *
359 * returns group number on success
360 * returns -1 on failure.
361 */
362int join_groupchat(Group_Chats *g_c, int32_t friendnumber, const uint8_t *data, uint16_t length)
363{
364 if (length != sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH)
365 return -1;
366
367 int groupnumber = create_group_chat(g_c);
368
369 if (groupnumber == -1)
370 return -1;
371
372 Group_c *g = &g_c->chats[groupnumber];
373
374 uint16_t group_num = htons(groupnumber);
375 g->status = GROUPCHAT_STATUS_VALID;
376 uint8_t response[INVITE_RESPONSE_PACKET_SIZE];
377 response[0] = INVITE_RESPONSE_ID;
378 memcpy(response + 1, &group_num, sizeof(uint16_t));
379 memcpy(response + 1 + sizeof(uint16_t), data, sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH);
380
381 if (send_group_invite_packet(g_c->m, friendnumber, response, sizeof(response))) {
382 uint16_t other_groupnum;
383 memcpy(&other_groupnum, data, sizeof(other_groupnum));
384 other_groupnum = htons(other_groupnum);
385 memcpy(g->identifier, data + sizeof(uint16_t), GROUP_IDENTIFIER_LENGTH);
386 add_friend_to_groupchat(g_c, friendnumber, groupnumber, other_groupnum);
387 g->peer_number = rand(); /* TODO */
388 return groupnumber;
389 } else {
390 return -1;
391 }
392}
393
394/* Set the callback for group invites.
395 *
396 * Function(Group_Chats *g_c, int32_t friendnumber, uint8_t *data, uint16_t length, void *userdata)
397 *
398 * data of length is what needs to be passed to join_groupchat().
399 */
400void g_callback_group_invite(Group_Chats *g_c, void (*function)(Messenger *m, int32_t, const uint8_t *, uint16_t,
401 void *), void *userdata)
402{
403 g_c->invite_callback = function;
404 g_c->invite_callback_userdata = userdata;
405}
406
407/* Set the callback for group messages.
408 *
409 * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata)
410 */
411void g_callback_group_message(Group_Chats *g_c, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_t,
412 void *), void *userdata)
413{
414 g_c->message_callback = function;
415 g_c->message_callback_userdata = userdata;
416}
417
418static void handle_friend_invite_packet(Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length)
419{
420 Group_Chats *g_c = m->group_chat_object;
421
422 if (length <= 1)
423 return;
424
425 const uint8_t *invite_data = data + 1;
426 uint16_t invite_length = length - 1;
427
428 switch (data[0]) {
429 case INVITE_ID: {
430 if (length != INVITE_PACKET_SIZE)
431 return;
432
433 int groupnumber = get_group_num(g_c, data + 1 + sizeof(uint16_t));
434
435 if (groupnumber == -1) {
436 if (g_c->invite_callback)
437 g_c->invite_callback(m, friendnumber, invite_data, invite_length, g_c->invite_callback_userdata);
438
439 return;
440 } else {
441 //TODO
442 uint16_t other_groupnum;
443 memcpy(&other_groupnum, data + 1, sizeof(uint16_t));
444 other_groupnum = ntohs(other_groupnum);
445 add_friend_to_groupchat(g_c, friendnumber, groupnumber, other_groupnum);
446 }
447
448 break;
449 }
450
451 case INVITE_RESPONSE_ID: {
452 if (length != INVITE_RESPONSE_PACKET_SIZE)
453 return;
454
455 uint16_t other_groupnum, groupnum;
456 memcpy(&groupnum, data + 1 + sizeof(uint16_t), sizeof(uint16_t));
457 groupnum = ntohs(groupnum);
458
459 Group_c *g = get_group_c(g_c, groupnum);
460
461 if (!g)
462 return;
463
464 if (memcmp(data + 1 + sizeof(uint16_t) * 2, g->identifier, GROUP_IDENTIFIER_LENGTH) != 0)
465 return;
466
467 memcpy(&other_groupnum, data + 1, sizeof(uint16_t));
468 other_groupnum = ntohs(other_groupnum);
469
470 add_friend_to_groupchat(g_c, friendnumber, groupnum, other_groupnum);
471
472 break;
473 }
474
475 default:
476 return;
477 }
478}
479
480/* Find index of friend in the close list;
481 *
482 * returns index on success
483 * returns -1 on failure.
484 */
485static int friend_in_close(Group_c *g, int32_t friendnumber)
486{
487 int i;
488
489 for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
490 if (g->close[i].type != GROUPCHAT_CLOSE_CONNECTION)
491 continue;
492
493 if (g->close[i].number != (uint32_t)friendnumber)
494 continue;
495
496 return i;
497 }
498
499 return -1;
500}
501
502#define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1)
503
504/* Send message to all close except receiver (if receiver isn't -1)
505 * NOTE: this function appends the group chat number to the data passed to it.
506 *
507 * return number of messages sent.
508 */
509static unsigned int send_message_all_close(const Group_Chats *g_c, int groupnumber, const uint8_t *data,
510 uint16_t length, int receiver)
511{
512
513 Group_c *g = get_group_c(g_c, groupnumber);
514
515 if (!g)
516 return 0;
517
518 uint16_t i, sent = 0;
519
520 for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) {
521 if (g->close[i].type == GROUPCHAT_CLOSE_NONE)
522 continue;
523
524 if ((int)i == receiver)
525 continue;
526
527 uint16_t other_groupnum = htons(g->close[i].group_number);
528 uint8_t packet[sizeof(uint16_t) + length];
529 memcpy(packet, &other_groupnum, sizeof(uint16_t));
530 memcpy(packet + sizeof(uint16_t), data, length);
531
532 if (send_group_message_packet(g_c->m, g->close[i].number, packet, sizeof(packet)))
533 ++sent;
534 }
535
536 return sent;
537}
538
539#define MAX_GROUP_MESSAGE_DATA_LEN (MAX_CRYPTO_DATA_SIZE - (1 + MIN_MESSAGE_PACKET_LEN))
540
541/* Send data of len with message_id to groupnumber.
542 *
543 * return number of peers it was sent to on success.
544 * return 0 on failure.
545 */
546static unsigned int send_message_group(const Group_Chats *g_c, int groupnumber, uint8_t message_id, const uint8_t *data,
547 uint16_t len)
548{
549 if (len > MAX_GROUP_MESSAGE_DATA_LEN)
550 return 0;
551
552 Group_c *g = get_group_c(g_c, groupnumber);
553
554 if (!g)
555 return 0;
556
557 uint8_t packet[sizeof(uint16_t) + sizeof(uint32_t) + 1 + len];
558 uint16_t peer_num = htons(g->peer_number);
559 memcpy(packet, &peer_num, sizeof(peer_num));
560
561 ++g->message_number;
562
563 if (!g->message_number)
564 ++g->message_number;
565
566 uint32_t message_num = htonl(g->message_number);
567 memcpy(packet + sizeof(uint16_t), &message_num, sizeof(message_num));
568
569 packet[sizeof(uint16_t) + sizeof(uint32_t)] = message_id;
570
571 if (len)
572 memcpy(packet + sizeof(uint16_t) + sizeof(uint32_t) + 1, data, len);
573
574 return send_message_all_close(g_c, groupnumber, packet, sizeof(packet), -1);
575}
576
577/* send a group message
578 * return 0 on success
579 * return -1 on failure
580 */
581int group_message_send(const Group_Chats *g_c, int groupnumber, const uint8_t *message, uint16_t length)
582{
583 if (send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length)) {
584 return 0;
585 } else {
586 return -1;
587 }
588}
589
590static void handle_message_packet_group(Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length,
591 int close_index)
592{
593 if (length < MIN_MESSAGE_PACKET_LEN)
594 return;
595
596 Group_c *g = get_group_c(g_c, groupnumber);
597
598 if (!g)
599 return;
600
601 uint16_t peer_number;
602 memcpy(&peer_number, data + sizeof(uint16_t), sizeof(uint16_t));
603 peer_number = ntohs(peer_number);
604
605 int index = get_peer_index(g, peer_number);
606
607 //TODO remove
608 if (index == -1) {
609 uint8_t empty_key[crypto_box_PUBLICKEYBYTES];
610 index = addpeer(g, empty_key, peer_number);
611 }
612
613 if (index == -1)
614 return;
615
616 uint32_t message_number;
617 memcpy(&message_number, data + sizeof(uint16_t) * 2, sizeof(message_number));
618 message_number = ntohl(message_number);
619
620 if (g->group[index].last_message_number == 0) {
621 g->group[index].last_message_number = message_number;
622 } else if (message_number - g->group[index].last_message_number > 64 ||
623 message_number == g->group[index].last_message_number) {
624 return;
625 }
626
627 g->group[index].last_message_number = message_number;
628
629 uint8_t message_id = data[sizeof(uint16_t) * 2 + sizeof(message_number)];
630 const uint8_t *msg_data = data + sizeof(uint16_t) * 2 + sizeof(message_number) + 1;
631 uint16_t msg_data_len = length - (sizeof(uint16_t) * 2 + sizeof(message_number) + 1);
632
633 switch (message_id) {
634 case PACKET_ID_MESSAGE: {
635 if (msg_data_len == 0)
636 return;
637
638 //TODO
639 if (g_c->message_callback)
640 g_c->message_callback(g_c->m, groupnumber, index, msg_data, msg_data_len, g_c->message_callback_userdata);
641
642 break;
643 }
644
645 default:
646 return;
647 }
648
649 send_message_all_close(g_c, groupnumber, data + sizeof(uint16_t), length - sizeof(uint16_t), close_index);
650}
651
652static void handle_friend_message_packet(Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length)
653{
654 Group_Chats *g_c = m->group_chat_object;
655
656 if (length < MIN_MESSAGE_PACKET_LEN)
657 return;
658
659 uint16_t groupnumber;
660 memcpy(&groupnumber, data, sizeof(uint16_t));
661 groupnumber = ntohs(groupnumber);
662 Group_c *g = get_group_c(g_c, groupnumber);
663
664 if (!g)
665 return;
666
667 int index = friend_in_close(g, friendnumber);
668
669 if (index == -1)
670 return;
671
672 handle_message_packet_group(g_c, groupnumber, data, length, index);
673}
674
675static int handle_packet(void *object, int number, uint8_t *data, uint16_t length)
676{
677 if (length <= 1)
678 return -1;
679
680 switch (data[0]) {
681 case PACKET_ID_INVITE_GROUPCHAT: {
682 handle_friend_invite_packet(object, number, data + 1, length - 1);
683 break;
684 }
685
686 case PACKET_ID_MESSAGE_GROUPCHAT: {
687 handle_friend_message_packet(object, number, data + 1, length - 1);
688 break;
689 }
690
691 default: {
692 return 0;
693 }
694 }
695
696 return 0;
697}
698
699
700/* Create new groupchat instance. */
701Group_Chats *new_groupchats(Messenger *m)
702{
703 if (!m)
704 return NULL;
705
706 Group_Chats *temp = calloc(1, sizeof(Group_Chats));
707
708 if (temp == NULL)
709 return NULL;
710
711 temp->m = m;
712 m->group_chat_object = temp;
713 m_callback_group_invite(m, &handle_friend_invite_packet);
714
715 return temp;
716}
717
718/* main groupchats loop. */
719void do_groupchats(Group_Chats *g_c)
720{
721 //TODO
722}
723
724/* Free everything related with group chats. */
725void kill_groupchats(Group_Chats *g_c)
726{
727 //TODO
728 g_c->m->group_chat_object = 0;
729 free(g_c);
730}
731
732/* Return the number of chats in the instance m.
733 * You should use this to determine how much memory to allocate
734 * for copy_chatlist. */
735/*
736uint32_t count_chatlist(const Messenger *m)
737{
738 uint32_t ret = 0;
739 uint32_t i;
740
741 for (i = 0; i < m->numchats; i++) {
742 if (m->chats[i]) {
743 ret++;
744 }
745 }
746
747 return ret;
748}*/
749
750/* Copy a list of valid chat IDs into the array out_list.
751 * If out_list is NULL, returns 0.
752 * Otherwise, returns the number of elements copied.
753 * If the array was too small, the contents
754 * of out_list will be truncated to list_size. */
755/*
756uint32_t copy_chatlist(const Messenger *m, int *out_list, uint32_t list_size)
757{
758 if (!out_list)
759 return 0;
760
761 if (m->numchats == 0) {
762 return 0;
763 }
764
765 uint32_t i;
766 uint32_t ret = 0;
767
768 for (i = 0; i < m->numchats; i++) {
769 if (ret >= list_size) {
770 break; *//* Abandon ship *//*
771 }
772
773 if (m->chats[i]) {
774 out_list[ret] = i;
775 ret++;
776 }
777 }
778
779 return ret;
780}
781*/ \ No newline at end of file