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