#include "friend.h" #include "avatar.h" #include "chatlog.h" #include "debug.h" #include "filesys.h" #include "flist.h" #include "macros.h" #include "self.h" #include "settings.h" #include "text.h" #include "tox.h" #include "utox.h" #include "av/audio.h" #include "layout/friend.h" // TODO, remove this and sent the name differently #include "native/image.h" #include "native/notify.h" #include "ui/edit.h" // friend_set_name() #include #include uint8_t addfriend_status; static FRIEND *friend = NULL; FRIEND *get_friend(uint32_t friend_number) { if (friend_number >= self.friend_list_size) { LOG_WARN("Friend", "Friend number (%u) out of bounds.", friend_number); return NULL; } return &friend[friend_number]; } static FRIEND *friend_make(uint32_t friend_number) { if (friend_number >= self.friend_list_size) { LOG_INFO("Friend", "Reallocating friend array to %u. Current size: %u", (friend_number + 1), self.friend_list_size); FRIEND *tmp = realloc(friend, sizeof(FRIEND) * (friend_number + 1)); if (!tmp) { LOG_ERR("Friend", "Could not reallocate friends array."); return NULL; } friend = tmp; self.friend_list_size = friend_number + 1; } // TODO should we memset(0); before return? return &friend[friend_number]; } static FREQUEST *frequests = NULL; static uint16_t frequest_list_size = 0; FREQUEST *get_frequest(uint16_t frequest_number) { if (frequest_number >= frequest_list_size) { LOG_ERR("Friend", "Request number out of bounds."); return NULL; } return &frequests[frequest_number]; } static FREQUEST *frequest_make(uint16_t frequest_number) { if (frequest_number >= frequest_list_size) { LOG_INFO("Friend", "Reallocating frequest array to %u. Current size: %u", (frequest_number + 1), frequest_list_size); FREQUEST *tmp = realloc(frequests, sizeof(FREQUEST) * (frequest_number + 1)); if (!tmp) { LOG_ERR("Friend", "Could not reallocate frequests array."); return NULL; } frequests = tmp; frequest_list_size = frequest_number + 1; } // TODO should we memset(0); before return? return &frequests[frequest_number]; } uint16_t friend_request_new(const uint8_t *id, const uint8_t *msg, size_t length) { uint16_t curr_num = frequest_list_size; FREQUEST *r = frequest_make(frequest_list_size); // TODO search for empty request slots if (!r) { LOG_ERR("Friend", "Unable to get space for Friend Request."); return UINT16_MAX; } r->number = curr_num; memcpy(r->bin_id, id, TOX_ADDRESS_SIZE); r->msg = malloc(length + 1); if (!r->msg) { LOG_ERR("Friend", "Unable to get space for friend request message."); return UINT16_MAX; } memcpy(r->msg, msg, length); r->msg[length] = 0; // Toxcore doesn't promise null term on strings r->length = length; return curr_num; } void friend_request_free(uint16_t number) { FREQUEST *r = get_frequest(number); if (!r) { LOG_ERR("Friend", "Unable to free a missing request."); return; } free(r->msg); // TODO this needs a test if (r->number >= frequest_list_size -1) { FREQUEST *tmp = realloc(frequests, sizeof(FREQUEST) * (frequest_list_size - 1)); if (tmp) { frequests = tmp; --frequest_list_size; } } } /* TODO incoming friends "leaks" */ void free_friends(void) { for (uint32_t i = 0; i < self.friend_list_count; i++){ FRIEND *f = get_friend(i); if (!f) { LOG_WARN("Friend", "Could not get friend %u. Skipping", i); continue; } friend_free(f); } if (friend) { free(friend); } } void utox_write_metadata(FRIEND *f) { /* Create path */ char dest[UTOX_FILE_NAME_LENGTH]; snprintf(dest, UTOX_FILE_NAME_LENGTH, "%.*s.fmetadata", TOX_PUBLIC_KEY_SIZE * 2, f->id_str); FILE *file = utox_get_file(dest, NULL, UTOX_FILE_OPTS_WRITE); if (!file) { LOG_ERR("Friend", "Unable to get file to write metadata for friend %u", f->number); return; } FRIEND_META_DATA metadata = { 0 }; size_t total_size = sizeof(metadata); metadata.version = METADATA_VERSION; metadata.ft_autoaccept = f->ft_autoaccept; metadata.skip_msg_logging = f->skip_msg_logging; if (f->alias && f->alias_length) { metadata.alias_length = f->alias_length; total_size += metadata.alias_length; } uint8_t *data = calloc(1, total_size); if (data) { memcpy(data, &metadata, sizeof(metadata)); if (f->alias && f->alias_length) { memcpy(data + sizeof(metadata), f->alias, metadata.alias_length); } fwrite(data, total_size, 1, file); free(data); } fclose(file); } static void friend_meta_data_read(FRIEND *f) { /* Will need to be rewritten if anything is added to friend's meta data */ char path[UTOX_FILE_NAME_LENGTH]; snprintf(path, UTOX_FILE_NAME_LENGTH, "%.*s.fmetadata", TOX_PUBLIC_KEY_SIZE * 2, f->id_str); size_t size = 0; FILE *file = utox_get_file(path, &size, UTOX_FILE_OPTS_READ); if (!file) { LOG_TRACE("Friend", "Meta Data not found %s", path); return; } if (size < sizeof(FRIEND_META_DATA)) { LOG_ERR("Metadata", "Stored metadata is incomplete."); fclose(file); return; } FRIEND_META_DATA *metadata = calloc(1, size); if (!metadata) { LOG_ERR("Metadata", "Could not allocate memory for metadata." ); fclose(file); return; } bool read_meta = fread(metadata, size, 1, file); fclose(file); if (!read_meta) { LOG_ERR("Metadata", "Failed to read metadata from disk."); free(metadata); return; } if (metadata->version != 0) { LOG_ERR("Metadata", "WARNING! This version of utox does not support this metadata file version." ); free(metadata); return; } if (metadata->alias_length) { friend_set_alias(f, &metadata->data[0], metadata->alias_length); } else { friend_set_alias(f, NULL, 0); /* uTox expects this to be 0/NULL if there's no alias. */ } f->ft_autoaccept = metadata->ft_autoaccept; free(metadata); return; } void utox_friend_init(Tox *tox, uint32_t friend_number) { LOG_INFO("Friend", "Initializing friend: %u", friend_number); FRIEND *f = friend_make(friend_number); // get friend pointer if (!f) { LOG_ERR("Friend", "Could not create init friend %u", friend_number); return; } self.friend_list_count++; uint8_t name[TOX_MAX_NAME_LENGTH]; memset(f, 0, sizeof(FRIEND)); // Get and set the public key for this friend number and set it. tox_friend_get_public_key(tox, friend_number, f->id_bin, 0); cid_to_string(f->id_str, f->id_bin); // Set the friend number we got from toxcore f->number = friend_number; // Get and set friend name and length int size = tox_friend_get_name_size(tox, friend_number, 0); tox_friend_get_name(tox, friend_number, name, 0); // Set the name for utox as well friend_setname(f, name, size); // Get and set the status message size = tox_friend_get_status_message_size(tox, friend_number, 0); f->status_message = calloc(1, size); if (!f->status_message) { LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not alloc for status message (%uB)", size); } tox_friend_get_status_message(tox, friend_number, (uint8_t *)f->status_message, 0); f->status_length = size; /* TODO; consider error handling these two */ f->online = tox_friend_get_connection_status(tox, friend_number, NULL); f->status = tox_friend_get_status(tox, friend_number, NULL); f->avatar = calloc(1, sizeof(AVATAR)); if (!f->avatar) { LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not alloc for avatar"); } avatar_init(f->id_str, f->avatar); MESSAGES *m = &f->msg; messages_init(m, friend_number); // Set scroll position to bottom of window. f->msg.scroll = 1.0; f->msg.panel.type = PANEL_MESSAGES; f->msg.panel.content_scroll = &scrollbar_friend; f->msg.panel.y = MAIN_TOP; f->msg.panel.height = CHAT_BOX_TOP; f->msg.panel.width = -SCROLL_WIDTH; // Get the chat backlog messages_read_from_log(friend_number); // Load the meta data, if it exists. friend_meta_data_read(f); } void utox_friend_list_init(Tox *tox) { LOG_INFO("Friend", "Initializing friend list."); self.friend_list_size = tox_self_get_friend_list_size(tox); friend = calloc(self.friend_list_size, sizeof(FRIEND)); if (!friend) { LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not allocate friend list with size: %u", self.friend_list_size); } for (uint32_t i = 0; i < self.friend_list_size; ++i) { utox_friend_init(tox, i); } LOG_INFO("Friend", "Friendlist successfully initialized with %u friends.", self.friend_list_size); } void friend_setname(FRIEND *f, uint8_t *name, size_t length) { if (f->name && f->name_length) { char *p; size_t p_size = sizeof(" is now known as ") + f->name_length + length; p = calloc(1, p_size); if (!p) { LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not alloc space for name change message (%uB)", p_size); } snprintf(p, p_size, "%.*s is now known as %.*s", (int)f->name_length, f->name, (int)length, name); size_t p_len = strnlen(p, p_size - 1); if (length != f->name_length || memcmp(f->name, name, (length < f->name_length ? length : f->name_length))) { message_add_type_notice(&f->msg, p, p_len, 1); } free(f->name); free(p); } if (length == 0) { f->name = calloc(1, TOX_PUBLIC_KEY_SIZE * 2 + 1); cid_to_string(f->name, f->id_bin); f->name_length = TOX_PUBLIC_KEY_SIZE * 2; } else { f->name = calloc(1, length + 1); memcpy(f->name, name, length); f->name_length = length; } if (!f->name) { LOG_FATAL_ERR(EXIT_MALLOC, "Friend", "Could not alloc space for friend name"); } f->name[f->name_length] = '\0'; if (!f->alias_length) { if (flist_get_type()== ITEM_FRIEND) { FRIEND *selected = flist_get_friend(); if (!selected) { LOG_ERR("Friend", "Unable to get selected friend."); return; } if (selected && f->number == selected->number) { maybe_i18nal_string_set_plain(&edit_friend_alias.empty_str, f->name, f->name_length); } } } flist_update_shown_list(); } void friend_set_alias(FRIEND *f, uint8_t *alias, uint16_t length) { if (length > 0) { if (!alias) { LOG_ERR("Friend Alias", "Got alias length, but no alias."); return; } LOG_TRACE("Friend", "New Alias set for friend %s." , f->name); } else { LOG_TRACE("Friend", "Alias for friend %s unset." , f->name); } free(f->alias); if (length == 0) { f->alias = NULL; f->alias_length = 0; } else { f->alias = calloc(1, length + 1); if (!f->alias) { LOG_ERR("Friend", "Unable to malloc for alias set for friend %s."); return; } memcpy(f->alias, alias, length); f->alias_length = length; } } void friend_sendimage(FRIEND *f, NATIVE_IMAGE *native_image, uint16_t width, uint16_t height, UTOX_IMAGE png_image, size_t png_size) { struct TOX_SEND_INLINE_MSG *tsim = malloc(sizeof(struct TOX_SEND_INLINE_MSG)); if (!tsim) { LOG_ERR("Friend", "Unable to malloc for inline image."); return; } tsim->image = png_image; tsim->image_size = png_size; postmessage_toxcore(TOX_FILE_SEND_NEW_INLINE, f - friend, 0, tsim); message_add_type_image(&f->msg, 1, native_image, width, height, 0); } void friend_recvimage(FRIEND *f, NATIVE_IMAGE *native_image, uint16_t width, uint16_t height) { if (!NATIVE_IMAGE_IS_VALID(native_image)) { return; } message_add_type_image(&f->msg, 0, native_image, width, height, 0); } void friend_notify_msg(FRIEND *f, const char *msg, size_t msg_length) { char title[sizeof("uTox new message from ") + UTOX_FRIEND_NAME_LENGTH(f)]; snprintf((char *)title, sizeof(title), "uTox new message from %.*s", (int)UTOX_FRIEND_NAME_LENGTH(f), UTOX_FRIEND_NAME(f)); size_t title_length = strnlen(title, sizeof(title) - 1); postmessage_utox(FRIEND_MESSAGE, f->number, 0, NULL); notify(title, title_length, msg, msg_length, f, 0); if (flist_get_friend() != f) { f->unread_msg = true; } if (flist_get_friend() != f || !have_focus) { postmessage_audio(UTOXAUDIO_PLAY_NOTIFICATION, NOTIFY_TONE_FRIEND_NEW_MSG, 0, NULL); } } bool friend_set_online(FRIEND *f, bool online) { if (f->online == online) { return false; } f->online = online; if (!f->online) { friend_set_typing(f, 0); } flist_update_shown_list(); return true; } void friend_set_typing(FRIEND *f, int typing) { f->typing = typing; } void friend_addid(uint8_t *id, char *msg, uint16_t msg_length) { char *data = malloc(TOX_ADDRESS_SIZE + msg_length); if (!data) { LOG_ERR("Friend", "Unable to malloc for friend request."); return; } memcpy(data, id, TOX_ADDRESS_SIZE); memcpy(data + TOX_ADDRESS_SIZE, msg, msg_length); postmessage_toxcore(TOX_FRIEND_NEW, msg_length, 0, data); } void friend_add(char *name, uint16_t length, char *msg, uint16_t msg_length) { if (!length) { addfriend_status = ADDF_NONAME; return; } uint8_t name_cleaned[length]; uint16_t length_cleaned = 0; for (unsigned int i = 0; i < length; ++i) { if (name[i] != ' ') { name_cleaned[length_cleaned] = name[i]; ++length_cleaned; } } if (!length_cleaned) { addfriend_status = ADDF_NONAME; return; } uint8_t id[TOX_ADDRESS_SIZE]; if (length_cleaned == TOX_ADDRESS_SIZE * 2 && string_to_id(id, (char *)name_cleaned)) { friend_addid(id, msg, msg_length); } else if (length_cleaned == TOX_PUBLIC_KEY_SIZE * 2) { string_to_id(id, (char*)name_cleaned); uint8_t *data = calloc(sizeof(uint8_t), TOX_PUBLIC_KEY_SIZE); if (!data) { LOG_ERR("Calloc", "Memory allocation failed!"); return; } memcpy(data, id, TOX_PUBLIC_KEY_SIZE); postmessage_toxcore(TOX_FRIEND_NEW_NO_REQ, TOX_PUBLIC_KEY_SIZE, 0, data); addfriend_status = ADDF_NOFREQUESTSENT; } else { addfriend_status = ADDF_BADNAME; } } void friend_history_clear(FRIEND *f) { if (!f) { LOG_ERR("Friend", "Unable to clear history for missing friend."); return; } messages_clear_all(&f->msg); utox_remove_friend_chatlog(f->id_str); } void friend_free(FRIEND *f) { LOG_INFO("Friend", "Freeing friend: %u", f->number); for (uint16_t i = 0; i < f->edit_history_length; ++i) { free(f->edit_history[i]); f->edit_history[i] = NULL; } free(f->edit_history); free(f->name); free(f->status_message); free(f->typed); free(f->avatar); for (uint32_t i = 0; i < f->msg.number; ++i) { MSG_HEADER *msg = f->msg.data[i]; message_free(msg); } free(f->msg.data); if (f->call_state_self) { // postmessage_audio(AUDIO_END, f->number, 0, NULL); /* TODO end a video call too! if(f->calling == CALL_OK_VIDEO) { postmessage_video(VIDEO_CALL_END, f->number, 0, NULL); }*/ } memset(f, 0, sizeof(FRIEND)); self.friend_list_count--; } FRIEND *find_friend_by_name(uint8_t *name) { for (size_t i = 0; i < self.friend_list_count; i++) { FRIEND *f = get_friend(i); if (!f) { LOG_ERR("Friend", "Could not get friend %u", i); continue; } if ((f->alias && memcmp(f->alias, name, MIN(f->alias_length, strlen((char *)name))) == 0) || memcmp(f->name, name, MIN(f->name_length, strlen((char *)name))) == 0) { return f; } } return NULL; } FRIEND *get_friend_by_id(const char *id_str) { for (size_t i = 0; i < self.friend_list_count; i++) { FRIEND *f = get_friend(i); if (!f) { LOG_ERR("Friend", "Could not get friend %u", i); continue; } if (strncmp(f->id_str, id_str, TOX_PUBLIC_KEY_SIZE * 2) == 0) { return f; } } return NULL; } void friend_notify_status(FRIEND *f, const uint8_t *msg, size_t msg_length, char *state) { if (!settings.status_notifications) { return; } char title[UTOX_FRIEND_NAME_LENGTH(f) + SLEN(STATUS_MESSAGE) + strlen(state)]; snprintf(title, sizeof(title), S(STATUS_MESSAGE), (int)UTOX_FRIEND_NAME_LENGTH(f), UTOX_FRIEND_NAME(f), state); size_t title_length = strnlen(title, sizeof(title) - 1); notify(title, title_length, (char *)msg, msg_length, f, 0); /* This function is called before the status is changed. so we have to go by the inverse * obviously not ideal, TODO fix later with the friends struct refactor. */ if (f->online) { postmessage_audio(UTOXAUDIO_PLAY_NOTIFICATION, NOTIFY_TONE_FRIEND_OFFLINE, 0, NULL); } else { postmessage_audio(UTOXAUDIO_PLAY_NOTIFICATION, NOTIFY_TONE_FRIEND_ONLINE, 0, NULL); } } bool string_to_id(uint8_t *w, char *a) { uint8_t *end = w + TOX_ADDRESS_SIZE; while (w != end) { char c, v; c = *a++; if (c >= '0' && c <= '9') { v = (c - '0') << 4; } else if (c >= 'A' && c <= 'F') { v = (c - 'A' + 10) << 4; } else if (c >= 'a' && c <= 'f') { v = (c - 'a' + 10) << 4; } else { return false; } c = *a++; if (c >= '0' && c <= '9') { v |= (c - '0'); } else if (c >= 'A' && c <= 'F') { v |= (c - 'A' + 10); } else if (c >= 'a' && c <= 'f') { v |= (c - 'a' + 10); } else { return false; } *w++ = v; } return true; } void cid_to_string(char *dest, uint8_t *src) { to_hex(dest, src, TOX_PUBLIC_KEY_SIZE); }