/*
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);
}