1 #include "friend.h"
2 
3 #include "avatar.h"
4 #include "chatlog.h"
5 #include "debug.h"
6 #include "filesys.h"
7 #include "flist.h"
8 #include "macros.h"
9 #include "self.h"
10 #include "settings.h"
11 #include "text.h"
12 #include "tox.h"
13 #include "utox.h"
14 
15 #include "av/audio.h"
16 
17 #include "layout/friend.h"  // TODO, remove this and sent the name differently
18 
19 #include "native/image.h"
20 #include "native/notify.h"
21 
22 #include "ui/edit.h"        // friend_set_name()
23 
24 #include <stdlib.h>
25 #include <string.h>
26 
27 uint8_t addfriend_status;
28 
29 static FRIEND *friend = NULL;
30 
get_friend(uint32_t friend_number)31 FRIEND *get_friend(uint32_t friend_number) {
32     if (friend_number >= self.friend_list_size) {
33         LOG_WARN("Friend", "Friend number (%u) out of bounds.", friend_number);
34         return NULL;
35     }
36 
37     return &friend[friend_number];
38 }
39 
friend_make(uint32_t friend_number)40 static FRIEND *friend_make(uint32_t friend_number) {
41     if (friend_number >= self.friend_list_size) {
42         LOG_INFO("Friend", "Reallocating friend array to %u. Current size: %u", (friend_number + 1), self.friend_list_size);
43         FRIEND *tmp = realloc(friend, sizeof(FRIEND) * (friend_number + 1));
44         if (!tmp) {
45             LOG_ERR("Friend", "Could not reallocate friends array.");
46             return NULL;
47         }
48 
49         friend = tmp;
50 
51         self.friend_list_size = friend_number + 1;
52 
53     }
54 
55     // TODO should we memset(0); before return?
56     return &friend[friend_number];
57 }
58 
59 static FREQUEST *frequests = NULL;
60 static uint16_t frequest_list_size = 0;
61 
get_frequest(uint16_t frequest_number)62 FREQUEST *get_frequest(uint16_t frequest_number) {
63     if (frequest_number >= frequest_list_size) {
64         LOG_ERR("Friend", "Request number out of bounds.");
65         return NULL;
66     }
67 
68     return &frequests[frequest_number];
69 }
70 
frequest_make(uint16_t frequest_number)71 static FREQUEST *frequest_make(uint16_t frequest_number) {
72     if (frequest_number >= frequest_list_size) {
73         LOG_INFO("Friend", "Reallocating frequest array to %u. Current size: %u", (frequest_number + 1), frequest_list_size);
74         FREQUEST *tmp = realloc(frequests, sizeof(FREQUEST) * (frequest_number + 1));
75         if (!tmp) {
76             LOG_ERR("Friend", "Could not reallocate frequests array.");
77             return NULL;
78         }
79 
80         frequests = tmp;
81         frequest_list_size = frequest_number + 1;
82     }
83 
84     // TODO should we memset(0); before return?
85     return &frequests[frequest_number];
86 }
87 
friend_request_new(const uint8_t * id,const uint8_t * msg,size_t length)88 uint16_t friend_request_new(const uint8_t *id, const uint8_t *msg, size_t length) {
89     uint16_t curr_num = frequest_list_size;
90     FREQUEST *r = frequest_make(frequest_list_size); // TODO search for empty request slots
91     if (!r) {
92         LOG_ERR("Friend", "Unable to get space for Friend Request.");
93         return UINT16_MAX;
94     }
95 
96     r->number = curr_num;
97     memcpy(r->bin_id, id, TOX_ADDRESS_SIZE);
98     r->msg = malloc(length + 1);
99     if (!r->msg) {
100         LOG_ERR("Friend", "Unable to get space for friend request message.");
101         return UINT16_MAX;
102     }
103     memcpy(r->msg, msg, length);
104     r->msg[length] = 0; // Toxcore doesn't promise null term on strings
105     r->length = length;
106 
107     return curr_num;
108 }
109 
friend_request_free(uint16_t number)110 void friend_request_free(uint16_t number) {
111     FREQUEST *r = get_frequest(number);
112     if (!r) {
113         LOG_ERR("Friend", "Unable to free a missing request.");
114         return;
115     }
116 
117     free(r->msg);
118 
119     // TODO this needs a test
120     if (r->number >= frequest_list_size -1) {
121         FREQUEST *tmp = realloc(frequests, sizeof(FREQUEST) * (frequest_list_size - 1));
122         if (tmp) {
123             frequests = tmp;
124             --frequest_list_size;
125         }
126     }
127 }
128 
129 /* TODO incoming friends "leaks" */
130 
free_friends(void)131 void free_friends(void) {
132     for (uint32_t i = 0; i < self.friend_list_count; i++){
133         FRIEND *f = get_friend(i);
134         if (!f) {
135             LOG_WARN("Friend", "Could not get friend %u. Skipping", i);
136             continue;
137         }
138         friend_free(f);
139     }
140 
141     if (friend) {
142         free(friend);
143     }
144 }
145 
utox_write_metadata(FRIEND * f)146 void utox_write_metadata(FRIEND *f) {
147     /* Create path */
148     char dest[UTOX_FILE_NAME_LENGTH];
149     snprintf(dest, UTOX_FILE_NAME_LENGTH, "%.*s.fmetadata", TOX_PUBLIC_KEY_SIZE * 2, f->id_str);
150 
151     FILE *file = utox_get_file(dest, NULL, UTOX_FILE_OPTS_WRITE);
152     if (!file) {
153         LOG_ERR("Friend", "Unable to get file to write metadata for friend %u", f->number);
154         return;
155     }
156 
157     FRIEND_META_DATA metadata = { 0 };
158     size_t total_size = sizeof(metadata);
159 
160     metadata.version          = METADATA_VERSION;
161     metadata.ft_autoaccept    = f->ft_autoaccept;
162     metadata.skip_msg_logging = f->skip_msg_logging;
163 
164     if (f->alias && f->alias_length) {
165         metadata.alias_length = f->alias_length;
166         total_size += metadata.alias_length;
167     }
168 
169     uint8_t *data = calloc(1, total_size);
170     if (data) {
171         memcpy(data, &metadata, sizeof(metadata));
172         if (f->alias && f->alias_length) {
173             memcpy(data + sizeof(metadata), f->alias, metadata.alias_length);
174         }
175 
176         fwrite(data, total_size, 1, file);
177         free(data);
178     }
179 
180     fclose(file);
181 }
182 
friend_meta_data_read(FRIEND * f)183 static void friend_meta_data_read(FRIEND *f) {
184     /* Will need to be rewritten if anything is added to friend's meta data */
185     char path[UTOX_FILE_NAME_LENGTH];
186 
187     snprintf(path, UTOX_FILE_NAME_LENGTH, "%.*s.fmetadata", TOX_PUBLIC_KEY_SIZE * 2,  f->id_str);
188 
189     size_t size = 0;
190     FILE *file = utox_get_file(path, &size, UTOX_FILE_OPTS_READ);
191 
192     if (!file) {
193         LOG_TRACE("Friend", "Meta Data not found %s", path);
194         return;
195     }
196 
197     if (size < sizeof(FRIEND_META_DATA)) {
198         LOG_ERR("Metadata", "Stored metadata is incomplete.");
199         fclose(file);
200         return;
201     }
202 
203     FRIEND_META_DATA *metadata = calloc(1, size);
204     if (!metadata) {
205         LOG_ERR("Metadata", "Could not allocate memory for metadata." );
206         fclose(file);
207         return;
208     }
209 
210     bool read_meta = fread(metadata, size, 1, file);
211     fclose(file);
212 
213     if (!read_meta) {
214         LOG_ERR("Metadata", "Failed to read metadata from disk.");
215         free(metadata);
216         return;
217     }
218 
219     if (metadata->version != 0) {
220         LOG_ERR("Metadata", "WARNING! This version of utox does not support this metadata file version." );
221         free(metadata);
222         return;
223     }
224 
225     if (metadata->alias_length) {
226         friend_set_alias(f, &metadata->data[0], metadata->alias_length);
227     } else {
228         friend_set_alias(f, NULL, 0); /* uTox expects this to be 0/NULL if there's no alias. */
229     }
230 
231     f->ft_autoaccept = metadata->ft_autoaccept;
232 
233     free(metadata);
234     return;
235 }
236 
utox_friend_init(Tox * tox,uint32_t friend_number)237 void utox_friend_init(Tox *tox, uint32_t friend_number) {
238     LOG_INFO("Friend", "Initializing friend: %u", friend_number);
239     FRIEND *f = friend_make(friend_number); // get friend pointer
240     if (!f) {
241         LOG_ERR("Friend", "Could not create init friend %u", friend_number);
242         return;
243     }
244     self.friend_list_count++;
245     uint8_t name[TOX_MAX_NAME_LENGTH];
246 
247     memset(f, 0, sizeof(FRIEND));
248 
249     // Get and set the public key for this friend number and set it.
250     tox_friend_get_public_key(tox, friend_number, f->id_bin, 0);
251     cid_to_string(f->id_str, f->id_bin);
252 
253     // Set the friend number we got from toxcore
254     f->number = friend_number;
255 
256     // Get and set friend name and length
257     int size = tox_friend_get_name_size(tox, friend_number, 0);
258     tox_friend_get_name(tox, friend_number, name, 0);
259     // Set the name for utox as well
260     friend_setname(f, name, size);
261 
262     // Get and set the status message
263     size = tox_friend_get_status_message_size(tox, friend_number, 0);
264     f->status_message = calloc(1, size);
265     if (!f->status_message) {
266         LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not alloc for status message (%uB)", size);
267     }
268 
269     tox_friend_get_status_message(tox, friend_number, (uint8_t *)f->status_message, 0);
270     f->status_length = size;
271 
272     /* TODO; consider error handling these two */
273     f->online = tox_friend_get_connection_status(tox, friend_number, NULL);
274     f->status = tox_friend_get_status(tox, friend_number, NULL);
275 
276     f->avatar = calloc(1, sizeof(AVATAR));
277     if (!f->avatar) {
278         LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not alloc for avatar");
279     }
280     avatar_init(f->id_str, f->avatar);
281 
282     MESSAGES *m = &f->msg;
283     messages_init(m, friend_number);
284     // Set scroll position to bottom of window.
285     f->msg.scroll               = 1.0;
286     f->msg.panel.type           = PANEL_MESSAGES;
287     f->msg.panel.content_scroll = &scrollbar_friend;
288     f->msg.panel.y              = MAIN_TOP;
289     f->msg.panel.height         = CHAT_BOX_TOP;
290     f->msg.panel.width          = -SCROLL_WIDTH;
291     // Get the chat backlog
292     messages_read_from_log(friend_number);
293 
294     // Load the meta data, if it exists.
295     friend_meta_data_read(f);
296 }
297 
utox_friend_list_init(Tox * tox)298 void utox_friend_list_init(Tox *tox) {
299     LOG_INFO("Friend", "Initializing friend list.");
300 
301     self.friend_list_size = tox_self_get_friend_list_size(tox);
302 
303     friend = calloc(self.friend_list_size, sizeof(FRIEND));
304     if (!friend) {
305         LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not allocate friend list with size: %u", self.friend_list_size);
306     }
307 
308     for (uint32_t i = 0; i < self.friend_list_size; ++i) {
309         utox_friend_init(tox, i);
310     }
311     LOG_INFO("Friend", "Friendlist successfully initialized with %u friends.", self.friend_list_size);
312 }
313 
friend_setname(FRIEND * f,uint8_t * name,size_t length)314 void friend_setname(FRIEND *f, uint8_t *name, size_t length) {
315     if (f->name && f->name_length) {
316         char *p;
317         size_t p_size = sizeof(" is now known as ") + f->name_length + length;
318 
319         p = calloc(1, p_size);
320         if (!p) {
321             LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not alloc space for name change message (%uB)", p_size);
322         }
323         snprintf(p, p_size, "%.*s is now known as %.*s", (int)f->name_length, f->name, (int)length, name);
324         size_t p_len = strnlen(p, p_size - 1);
325 
326         if (length != f->name_length || memcmp(f->name, name, (length < f->name_length ? length : f->name_length))) {
327             message_add_type_notice(&f->msg, p, p_len, 1);
328         }
329 
330         free(f->name);
331         free(p);
332     }
333 
334     if (length == 0) {
335         f->name = calloc(1, TOX_PUBLIC_KEY_SIZE * 2 + 1);
336         cid_to_string(f->name, f->id_bin);
337         f->name_length = TOX_PUBLIC_KEY_SIZE * 2;
338     } else {
339         f->name = calloc(1, length + 1);
340         memcpy(f->name, name, length);
341         f->name_length = length;
342     }
343     if (!f->name) {
344         LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not alloc space for friend name");
345     }
346 
347     f->name[f->name_length] = '\0';
348 
349     if (!f->alias_length) {
350         if (flist_get_type()== ITEM_FRIEND) {
351             FRIEND *selected = flist_get_friend();
352             if (!selected) {
353                 LOG_ERR("Friend", "Unable to get selected friend.");
354                 return;
355             }
356             if (selected && f->number == selected->number) {
357                 maybe_i18nal_string_set_plain(&edit_friend_alias.empty_str, f->name, f->name_length);
358             }
359         }
360     }
361 
362     flist_update_shown_list();
363 }
364 
friend_set_alias(FRIEND * f,uint8_t * alias,uint16_t length)365 void friend_set_alias(FRIEND *f, uint8_t *alias, uint16_t length) {
366     if (length > 0) {
367         if (!alias) {
368             LOG_ERR("Friend Alias", "Got alias length, but no alias.");
369             return;
370         }
371 
372         LOG_TRACE("Friend", "New Alias set for friend %s." , f->name);
373     } else {
374         LOG_TRACE("Friend", "Alias for friend %s unset." , f->name);
375     }
376 
377     free(f->alias);
378     if (length == 0) {
379         f->alias        = NULL;
380         f->alias_length = 0;
381     } else {
382         f->alias = calloc(1, length + 1);
383         if (!f->alias) {
384             LOG_ERR("Friend", "Unable to malloc for alias set for friend %s.");
385             return;
386         }
387 
388         memcpy(f->alias, alias, length);
389         f->alias_length = length;
390     }
391 }
392 
friend_sendimage(FRIEND * f,NATIVE_IMAGE * native_image,uint16_t width,uint16_t height,UTOX_IMAGE png_image,size_t png_size)393 void friend_sendimage(FRIEND *f, NATIVE_IMAGE *native_image, uint16_t width, uint16_t height, UTOX_IMAGE png_image,
394                       size_t png_size) {
395     struct TOX_SEND_INLINE_MSG *tsim = malloc(sizeof(struct TOX_SEND_INLINE_MSG));
396     if (!tsim) {
397         LOG_ERR("Friend", "Unable to malloc for inline image.");
398         return;
399     }
400 
401     tsim->image      = png_image;
402     tsim->image_size = png_size;
403     postmessage_toxcore(TOX_FILE_SEND_NEW_INLINE, f - friend, 0, tsim);
404 
405     message_add_type_image(&f->msg, 1, native_image, width, height, 0);
406 }
407 
friend_recvimage(FRIEND * f,NATIVE_IMAGE * native_image,uint16_t width,uint16_t height)408 void friend_recvimage(FRIEND *f, NATIVE_IMAGE *native_image, uint16_t width, uint16_t height) {
409     if (!NATIVE_IMAGE_IS_VALID(native_image)) {
410         return;
411     }
412 
413     message_add_type_image(&f->msg, 0, native_image, width, height, 0);
414 }
415 
friend_notify_msg(FRIEND * f,const char * msg,size_t msg_length)416 void friend_notify_msg(FRIEND *f, const char *msg, size_t msg_length) {
417     char title[sizeof("uTox new message from ") + UTOX_FRIEND_NAME_LENGTH(f)];
418 
419     snprintf((char *)title, sizeof(title), "uTox new message from %.*s",
420              (int)UTOX_FRIEND_NAME_LENGTH(f), UTOX_FRIEND_NAME(f));
421     size_t title_length = strnlen(title, sizeof(title) - 1);
422 
423     postmessage_utox(FRIEND_MESSAGE, f->number, 0, NULL);
424     notify(title, title_length, msg, msg_length, f, 0);
425 
426     if (flist_get_friend() != f) {
427         f->unread_msg = true;
428     }
429 
430     if (flist_get_friend() != f || !have_focus) {
431         postmessage_audio(UTOXAUDIO_PLAY_NOTIFICATION, NOTIFY_TONE_FRIEND_NEW_MSG, 0, NULL);
432     }
433 }
434 
friend_set_online(FRIEND * f,bool online)435 bool friend_set_online(FRIEND *f, bool online) {
436     if (f->online == online) {
437         return false;
438     }
439 
440     f->online = online;
441     if (!f->online) {
442         friend_set_typing(f, 0);
443     }
444 
445     flist_update_shown_list();
446 
447     return true;
448 }
449 
450 
friend_set_typing(FRIEND * f,int typing)451 void friend_set_typing(FRIEND *f, int typing) {
452     f->typing = typing;
453 }
454 
friend_addid(uint8_t * id,char * msg,uint16_t msg_length)455 void friend_addid(uint8_t *id, char *msg, uint16_t msg_length) {
456     char *data = malloc(TOX_ADDRESS_SIZE + msg_length);
457     if (!data) {
458         LOG_ERR("Friend", "Unable to malloc for friend request.");
459         return;
460     }
461 
462     memcpy(data, id, TOX_ADDRESS_SIZE);
463     memcpy(data + TOX_ADDRESS_SIZE, msg, msg_length);
464 
465     postmessage_toxcore(TOX_FRIEND_NEW, msg_length, 0, data);
466 }
467 
friend_add(char * name,uint16_t length,char * msg,uint16_t msg_length)468 void friend_add(char *name, uint16_t length, char *msg, uint16_t msg_length) {
469     if (!length) {
470         addfriend_status = ADDF_NONAME;
471         return;
472     }
473 
474     uint8_t  name_cleaned[length];
475     uint16_t length_cleaned = 0;
476 
477     for (unsigned int i = 0; i < length; ++i) {
478         if (name[i] != ' ') {
479             name_cleaned[length_cleaned] = name[i];
480             ++length_cleaned;
481         }
482     }
483 
484     if (!length_cleaned) {
485         addfriend_status = ADDF_NONAME;
486         return;
487     }
488 
489     uint8_t id[TOX_ADDRESS_SIZE];
490     if (length_cleaned == TOX_ADDRESS_SIZE * 2 && string_to_id(id, (char *)name_cleaned)) {
491         friend_addid(id, msg, msg_length);
492     } else if (length_cleaned == TOX_PUBLIC_KEY_SIZE * 2) {
493         string_to_id(id, (char*)name_cleaned);
494         uint8_t *data = calloc(sizeof(uint8_t), TOX_PUBLIC_KEY_SIZE);
495         if (!data) {
496             LOG_ERR("Calloc", "Memory allocation failed!");
497             return;
498         }
499 
500         memcpy(data, id, TOX_PUBLIC_KEY_SIZE);
501         postmessage_toxcore(TOX_FRIEND_NEW_NO_REQ, TOX_PUBLIC_KEY_SIZE, 0, data);
502         addfriend_status = ADDF_NOFREQUESTSENT;
503     } else {
504         addfriend_status = ADDF_BADNAME;
505     }
506 }
507 
friend_history_clear(FRIEND * f)508 void friend_history_clear(FRIEND *f) {
509     if (!f) {
510         LOG_ERR("Friend", "Unable to clear history for missing friend.");
511         return;
512     }
513     messages_clear_all(&f->msg);
514     utox_remove_friend_chatlog(f->id_str);
515 }
516 
friend_free(FRIEND * f)517 void friend_free(FRIEND *f) {
518     LOG_INFO("Friend", "Freeing friend: %u", f->number);
519     for (uint16_t i = 0; i < f->edit_history_length; ++i) {
520         free(f->edit_history[i]);
521         f->edit_history[i] = NULL;
522     }
523     free(f->edit_history);
524 
525     free(f->name);
526     free(f->status_message);
527     free(f->typed);
528     free(f->avatar);
529 
530     for (uint32_t i = 0; i < f->msg.number; ++i) {
531         MSG_HEADER *msg = f->msg.data[i];
532         message_free(msg);
533     }
534     free(f->msg.data);
535 
536     if (f->call_state_self) {
537         // postmessage_audio(AUDIO_END, f->number, 0, NULL);
538         /* TODO end a video call too!
539         if(f->calling == CALL_OK_VIDEO) {
540             postmessage_video(VIDEO_CALL_END, f->number, 0, NULL);
541         }*/
542     }
543 
544     memset(f, 0, sizeof(FRIEND));
545     self.friend_list_count--;
546 }
547 
find_friend_by_name(uint8_t * name)548 FRIEND *find_friend_by_name(uint8_t *name) {
549     for (size_t i = 0; i < self.friend_list_count; i++) {
550         FRIEND *f = get_friend(i);
551         if (!f) {
552             LOG_ERR("Friend", "Could not get friend %u", i);
553             continue;
554         }
555 
556         if ((f->alias && memcmp(f->alias, name, MIN(f->alias_length, strlen((char *)name))) == 0)
557             || memcmp(f->name, name, MIN(f->name_length, strlen((char *)name))) == 0) {
558             return f;
559         }
560     }
561     return NULL;
562 }
563 
get_friend_by_id(const char * id_str)564 FRIEND *get_friend_by_id(const char *id_str) {
565     for (size_t i = 0; i < self.friend_list_count; i++) {
566         FRIEND *f = get_friend(i);
567         if (!f) {
568             LOG_ERR("Friend", "Could not get friend %u", i);
569             continue;
570         }
571 
572         if (strncmp(f->id_str, id_str, TOX_PUBLIC_KEY_SIZE * 2) == 0) {
573             return f;
574         }
575     }
576 
577     return NULL;
578 }
579 
friend_notify_status(FRIEND * f,const uint8_t * msg,size_t msg_length,char * state)580 void friend_notify_status(FRIEND *f, const uint8_t *msg, size_t msg_length, char *state) {
581     if (!settings.status_notifications) {
582         return;
583     }
584 
585     char title[UTOX_FRIEND_NAME_LENGTH(f) + SLEN(STATUS_MESSAGE) + strlen(state)];
586 
587     snprintf(title, sizeof(title), S(STATUS_MESSAGE),
588              (int)UTOX_FRIEND_NAME_LENGTH(f), UTOX_FRIEND_NAME(f), state);
589     size_t title_length = strnlen(title, sizeof(title) - 1);
590 
591     notify(title, title_length, (char *)msg, msg_length, f, 0);
592 
593     /* This function is called before the status is changed. so we have to go by the inverse
594      * obviously not ideal, TODO fix later with the friends struct refactor. */
595     if (f->online) {
596         postmessage_audio(UTOXAUDIO_PLAY_NOTIFICATION, NOTIFY_TONE_FRIEND_OFFLINE, 0, NULL);
597     } else {
598         postmessage_audio(UTOXAUDIO_PLAY_NOTIFICATION, NOTIFY_TONE_FRIEND_ONLINE, 0, NULL);
599     }
600 }
601 
string_to_id(uint8_t * w,char * a)602 bool string_to_id(uint8_t *w, char *a) {
603     uint8_t *end = w + TOX_ADDRESS_SIZE;
604     while (w != end) {
605         char c, v;
606 
607         c = *a++;
608         if (c >= '0' && c <= '9') {
609             v = (c - '0') << 4;
610         } else if (c >= 'A' && c <= 'F') {
611             v = (c - 'A' + 10) << 4;
612         } else if (c >= 'a' && c <= 'f') {
613             v = (c - 'a' + 10) << 4;
614         } else {
615             return false;
616         }
617 
618         c = *a++;
619         if (c >= '0' && c <= '9') {
620             v |= (c - '0');
621         } else if (c >= 'A' && c <= 'F') {
622             v |= (c - 'A' + 10);
623         } else if (c >= 'a' && c <= 'f') {
624             v |= (c - 'a' + 10);
625         } else {
626             return false;
627         }
628 
629         *w++ = v;
630     }
631 
632     return true;
633 }
634 
cid_to_string(char * dest,uint8_t * src)635 void cid_to_string(char *dest, uint8_t *src) {
636     to_hex(dest, src, TOX_PUBLIC_KEY_SIZE);
637 }
638