/* This file is part of GNUnet. Copyright (C) 2021--2024 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /* * @author Tobias Frisch * @file gnunet_chat_context.c */ #include "gnunet_chat_context.h" #include "gnunet_chat_file.h" #include "gnunet_chat_handle.h" #include "gnunet_chat_message.h" #include "gnunet_chat_util.h" #include "gnunet_chat_context_intern.c" #include #include #include #include #include static const unsigned int initial_map_size_of_room = 8; static const unsigned int initial_map_size_of_contact = 4; static void init_new_context (struct GNUNET_CHAT_Context *context, unsigned int initial_map_size) { GNUNET_assert(context); context->flags = 0; context->nick = NULL; context->topic = NULL; context->deleted = GNUNET_NO; context->request_task = NULL; context->timestamps = GNUNET_CONTAINER_multishortmap_create( initial_map_size, GNUNET_NO); context->dependencies = GNUNET_CONTAINER_multihashmap_create( initial_map_size, GNUNET_NO); context->messages = GNUNET_CONTAINER_multihashmap_create( initial_map_size, GNUNET_NO); context->requests = GNUNET_CONTAINER_multihashmap_create( initial_map_size, GNUNET_NO); context->taggings = GNUNET_CONTAINER_multihashmap_create( initial_map_size, GNUNET_NO); context->invites = GNUNET_CONTAINER_multihashmap_create( initial_map_size, GNUNET_NO); context->files = GNUNET_CONTAINER_multihashmap_create( initial_map_size, GNUNET_NO); context->user_pointer = NULL; context->member_pointers = GNUNET_CONTAINER_multishortmap_create( initial_map_size, GNUNET_NO); context->query = NULL; } struct GNUNET_CHAT_Context* context_create_from_room (struct GNUNET_CHAT_Handle *handle, struct GNUNET_MESSENGER_Room *room) { GNUNET_assert((handle) && (room)); struct GNUNET_CHAT_Context* context = GNUNET_new(struct GNUNET_CHAT_Context); context->handle = handle; context->type = GNUNET_CHAT_CONTEXT_TYPE_UNKNOWN; init_new_context(context, initial_map_size_of_room); context->room = room; context->contact = NULL; return context; } struct GNUNET_CHAT_Context* context_create_from_contact (struct GNUNET_CHAT_Handle *handle, const struct GNUNET_MESSENGER_Contact *contact) { GNUNET_assert((handle) && (contact)); struct GNUNET_CHAT_Context* context = GNUNET_new(struct GNUNET_CHAT_Context); context->handle = handle; context->type = GNUNET_CHAT_CONTEXT_TYPE_CONTACT; init_new_context(context, initial_map_size_of_contact); context->room = NULL; context->contact = contact; return context; } void context_destroy (struct GNUNET_CHAT_Context *context) { GNUNET_assert( (context) && (context->timestamps) && (context->dependencies) && (context->messages) && (context->taggings) && (context->invites) && (context->files) ); if (context->request_task) GNUNET_SCHEDULER_cancel(context->request_task); if (context->query) GNUNET_NAMESTORE_cancel(context->query); GNUNET_CONTAINER_multishortmap_iterate( context->timestamps, it_destroy_context_timestamps, NULL ); GNUNET_CONTAINER_multihashmap_clear(context->dependencies); GNUNET_CONTAINER_multihashmap_iterate( context->messages, it_destroy_context_messages, NULL ); GNUNET_CONTAINER_multihashmap_iterate( context->taggings, it_destroy_context_taggings, NULL ); GNUNET_CONTAINER_multihashmap_iterate( context->invites, it_destroy_context_invites, NULL ); GNUNET_CONTAINER_multishortmap_destroy(context->member_pointers); GNUNET_CONTAINER_multishortmap_destroy(context->timestamps); GNUNET_CONTAINER_multihashmap_destroy(context->dependencies); GNUNET_CONTAINER_multihashmap_destroy(context->messages); GNUNET_CONTAINER_multihashmap_destroy(context->requests); GNUNET_CONTAINER_multihashmap_destroy(context->taggings); GNUNET_CONTAINER_multihashmap_destroy(context->invites); GNUNET_CONTAINER_multihashmap_destroy(context->files); if (context->topic) GNUNET_free(context->topic); if (context->nick) GNUNET_free(context->nick); GNUNET_free(context); } void context_request_message (struct GNUNET_CHAT_Context* context, const struct GNUNET_HashCode *hash) { GNUNET_assert((context) && (hash)); if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put(context->requests, hash, NULL, GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE)) return; if (context->request_task) return; context->request_task = GNUNET_SCHEDULER_add_with_priority( GNUNET_SCHEDULER_PRIORITY_IDLE, cb_context_request_messages, context ); } void context_update_room (struct GNUNET_CHAT_Context *context, struct GNUNET_MESSENGER_Room *room, enum GNUNET_GenericReturnValue record) { GNUNET_assert(context); if (room == context->room) return; GNUNET_CONTAINER_multishortmap_iterate( context->timestamps, it_destroy_context_timestamps, NULL ); GNUNET_CONTAINER_multihashmap_iterate( context->messages, it_destroy_context_messages, NULL ); GNUNET_CONTAINER_multihashmap_iterate( context->invites, it_destroy_context_invites, NULL ); GNUNET_CONTAINER_multishortmap_destroy(context->timestamps); context->timestamps = GNUNET_CONTAINER_multishortmap_create( initial_map_size_of_room, GNUNET_NO); GNUNET_CONTAINER_multihashmap_clear(context->messages); GNUNET_CONTAINER_multihashmap_clear(context->requests); GNUNET_CONTAINER_multihashmap_clear(context->invites); GNUNET_CONTAINER_multihashmap_clear(context->files); if (context->room) context_delete(context, GNUNET_YES); context->room = room; if ((!(context->room)) || (GNUNET_YES != record)) return; context_write_records(context); } void context_update_nick (struct GNUNET_CHAT_Context *context, const char *nick) { GNUNET_assert(context); if (context->nick) GNUNET_free(context->nick); if (nick) context->nick = GNUNET_strdup(nick); else context->nick = NULL; if ((!(context->handle)) || (GNUNET_YES == context->deleted)) return; handle_send_internal_message( context->handle, NULL, context, GNUNET_CHAT_FLAG_UPDATE_CONTEXT, NULL ); } void context_read_records (struct GNUNET_CHAT_Context *context, const char *label, unsigned int count, const struct GNUNET_GNSRECORD_Data *data) { GNUNET_assert((context) && (context->room)); char *nick = NULL; char *topic = NULL; uint32_t flags = 0; for (unsigned int i = 0; i < count; i++) { if (!(GNUNET_GNSRECORD_RF_SUPPLEMENTAL & data[i].flags)) continue; if (GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS == data[i].record_type) { if (nick) continue; const struct GNUNET_MESSENGER_RoomDetailsRecord *record = data[i].data; nick = GNUNET_strndup(record->name, sizeof(record->name)); flags = record->flags; } if (GNUNET_DNSPARSER_TYPE_TXT == data[i].record_type) { if (topic) continue; topic = GNUNET_strndup(data[i].data, data[i].data_size); } } context->flags = flags; context_update_nick(context, nick); if (nick) GNUNET_free(nick); const struct GNUNET_HashCode *hash = GNUNET_MESSENGER_room_get_key( context->room ); if (topic) { struct GNUNET_HashCode topic_hash; GNUNET_CRYPTO_hash(topic, strlen(topic), &topic_hash); if (0 != GNUNET_CRYPTO_hash_cmp(&topic_hash, hash)) { GNUNET_free(topic); topic = NULL; } } util_set_name_field(topic, &(context->topic)); if (topic) GNUNET_free(topic); context->type = util_get_context_label_type(label, hash); } void context_delete_message (struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Message *message) { GNUNET_assert((context) && (message)); if (GNUNET_YES != message_has_msg(message)) return; switch (message->msg->header.kind) { case GNUNET_MESSENGER_KIND_INVITE: { struct GNUNET_CHAT_Invitation *invite = GNUNET_CONTAINER_multihashmap_get( context->invites, &(message->hash) ); if (! invite) break; if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove( context->invites, &(message->hash), invite)) invitation_destroy(invite); break; } case GNUNET_MESSENGER_KIND_FILE: { if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_contains(context->files, &(message->hash))) break; GNUNET_CONTAINER_multihashmap_remove_all(context->files, &(message->hash)); break; } case GNUNET_MESSENGER_KIND_TAG: { struct GNUNET_CHAT_Tagging *tagging = GNUNET_CONTAINER_multihashmap_get( context->taggings, &(message->msg->body.tag.hash) ); if (!tagging) break; tagging_remove(tagging, message); break; } default: break; } } void context_write_records (struct GNUNET_CHAT_Context *context) { GNUNET_assert((context) && (context->handle) && (context->room)); const struct GNUNET_CRYPTO_PrivateKey *zone = handle_get_key( context->handle ); if (!zone) return; const struct GNUNET_HashCode *hash = GNUNET_MESSENGER_room_get_key( context->room ); struct GNUNET_TIME_Absolute expiration = GNUNET_TIME_absolute_get_forever_(); struct GNUNET_MESSENGER_RoomEntryRecord room_entry; GNUNET_CRYPTO_get_peer_identity(context->handle->cfg, &(room_entry.door)); GNUNET_memcpy( &(room_entry.key), hash, sizeof(room_entry.key) ); struct GNUNET_MESSENGER_RoomDetailsRecord room_details; memset(room_details.name, 0, sizeof(room_details.name)); const char *topic = context->topic; if (topic) { struct GNUNET_HashCode topic_hash; GNUNET_CRYPTO_hash(topic, strlen(topic), &topic_hash); if (0 != GNUNET_CRYPTO_hash_cmp(&topic_hash, hash)) topic = NULL; } char *label; util_get_context_label(context->type, hash, &label); unsigned int count = 0; struct GNUNET_GNSRECORD_Data data [3]; if (GNUNET_YES == context->deleted) goto skip_record_data; data[count].record_type = GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_ENTRY; data[count].data = &room_entry; data[count].data_size = sizeof(room_entry); data[count].expiration_time = expiration.abs_value_us; data[count].flags = GNUNET_GNSRECORD_RF_PRIVATE; count++; if (context->nick) { size_t name_len = strlen(context->nick); if (name_len >= sizeof(room_details.name)) name_len = sizeof(room_details.name) - 1; GNUNET_memcpy(room_details.name, context->nick, name_len); room_details.name[name_len] = '\0'; } if ((context->nick) || (context->flags != 0)) { room_details.flags = context->flags; data[count].record_type = GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS; data[count].data = &room_details; data[count].data_size = sizeof(room_details); data[count].expiration_time = expiration.abs_value_us; data[count].flags = ( GNUNET_GNSRECORD_RF_PRIVATE | GNUNET_GNSRECORD_RF_SUPPLEMENTAL ); count++; } if (topic) { data[count].record_type = GNUNET_DNSPARSER_TYPE_TXT; data[count].data = topic; data[count].data_size = strlen(topic); data[count].expiration_time = expiration.abs_value_us; data[count].flags = ( GNUNET_GNSRECORD_RF_PRIVATE | GNUNET_GNSRECORD_RF_SUPPLEMENTAL ); count++; } skip_record_data: if (context->query) GNUNET_NAMESTORE_cancel(context->query); context->query = GNUNET_NAMESTORE_record_set_store( context->handle->namestore, zone, label, count, data, cont_context_write_records, context ); GNUNET_free(label); } void context_delete (struct GNUNET_CHAT_Context *context, enum GNUNET_GenericReturnValue exit) { GNUNET_assert((context) && (context->room)); context->deleted = GNUNET_YES; context_write_records(context); if (GNUNET_YES != exit) return; GNUNET_MESSENGER_close_room(context->room); }