libgnunetchat

library for GNUnet Messenger
Log | Files | Refs | README | LICENSE

gnunet_chat_context.c (15065B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2021--2025 GNUnet e.V.
      4 
      5    GNUnet is free software: you can redistribute it and/or modify it
      6    under the terms of the GNU Affero General Public License as published
      7    by the Free Software Foundation, either version 3 of the License,
      8    or (at your option) any later version.
      9 
     10    GNUnet is distributed in the hope that it will be useful, but
     11    WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13    Affero General Public License for more details.
     14 
     15    You should have received a copy of the GNU Affero General Public License
     16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18    SPDX-License-Identifier: AGPL3.0-or-later
     19  */
     20 /*
     21  * @author Tobias Frisch
     22  * @file gnunet_chat_context.c
     23  */
     24 
     25 #include "gnunet_chat_context.h"
     26 #include "gnunet_chat_file.h"
     27 #include "gnunet_chat_handle.h"
     28 #include "gnunet_chat_message.h"
     29 #include "gnunet_chat_util.h"
     30 
     31 #include "gnunet_chat_context_intern.c"
     32 #include <gnunet/gnunet_common.h>
     33 #include <gnunet/gnunet_messenger_service.h>
     34 #include <gnunet/gnunet_namestore_service.h>
     35 #include <gnunet/gnunet_scheduler_lib.h>
     36 #include <string.h>
     37 
     38 static const unsigned int initial_map_size_of_room = 8;
     39 static const unsigned int initial_map_size_of_contact = 4;
     40 
     41 static void
     42 init_new_context (struct GNUNET_CHAT_Context *context,
     43                   unsigned int initial_map_size)
     44 {
     45   GNUNET_assert(context);
     46 
     47   context->flags = 0;
     48   context->nick = NULL;
     49   context->topic = NULL;
     50   context->deleted = GNUNET_NO;
     51 
     52   context->request_task = NULL;
     53 
     54   context->timestamps = GNUNET_CONTAINER_multishortmap_create(
     55     initial_map_size, GNUNET_NO);
     56   context->dependencies = GNUNET_CONTAINER_multihashmap_create(
     57     initial_map_size, GNUNET_NO);
     58   context->messages = GNUNET_CONTAINER_multihashmap_create(
     59     initial_map_size, GNUNET_NO);
     60   context->requests = GNUNET_CONTAINER_multihashmap_create(
     61     initial_map_size, GNUNET_NO);
     62   context->taggings = GNUNET_CONTAINER_multihashmap_create(
     63     initial_map_size, GNUNET_NO);
     64   context->invites = GNUNET_CONTAINER_multihashmap_create(
     65     initial_map_size, GNUNET_NO);
     66   context->files = GNUNET_CONTAINER_multihashmap_create(
     67     initial_map_size, GNUNET_NO);
     68   context->discourses = GNUNET_CONTAINER_multishortmap_create(
     69     initial_map_size, GNUNET_NO);
     70   
     71   context->user_pointer = NULL;
     72 
     73   context->member_pointers = GNUNET_CONTAINER_multishortmap_create(
     74     initial_map_size, GNUNET_NO);
     75 
     76   context->query = NULL;
     77 }
     78 
     79 struct GNUNET_CHAT_Context*
     80 context_create_from_room (struct GNUNET_CHAT_Handle *handle,
     81 			                    struct GNUNET_MESSENGER_Room *room)
     82 {
     83   GNUNET_assert((handle) && (room));
     84 
     85   struct GNUNET_CHAT_Context* context = GNUNET_new(struct GNUNET_CHAT_Context);
     86 
     87   context->handle = handle;
     88   context->type = GNUNET_CHAT_CONTEXT_TYPE_UNKNOWN;
     89   
     90   init_new_context(context, initial_map_size_of_room);
     91 
     92   context->room = room;
     93   context->contact = NULL;
     94 
     95   union GNUNET_MESSENGER_RoomKey key;
     96   GNUNET_memcpy(
     97     &(key.hash),
     98     GNUNET_MESSENGER_room_get_key(room),
     99     sizeof (key.hash)
    100   );
    101 
    102   if (key.code.group_bit)
    103     context->type = GNUNET_CHAT_CONTEXT_TYPE_GROUP;
    104   else
    105     context->type = GNUNET_CHAT_CONTEXT_TYPE_CONTACT;
    106 
    107   return context;
    108 }
    109 
    110 struct GNUNET_CHAT_Context*
    111 context_create_from_contact (struct GNUNET_CHAT_Handle *handle,
    112 			                       const struct GNUNET_MESSENGER_Contact *contact)
    113 {
    114   GNUNET_assert((handle) && (contact));
    115 
    116   struct GNUNET_CHAT_Context* context = GNUNET_new(struct GNUNET_CHAT_Context);
    117 
    118   context->handle = handle;
    119   context->type = GNUNET_CHAT_CONTEXT_TYPE_CONTACT;
    120 
    121   init_new_context(context, initial_map_size_of_contact);
    122 
    123   context->room = NULL;
    124   context->contact = contact;
    125 
    126   return context;
    127 }
    128 
    129 void
    130 context_destroy (struct GNUNET_CHAT_Context *context)
    131 {
    132   GNUNET_assert(
    133     (context) &&
    134     (context->timestamps) &&
    135     (context->dependencies) &&
    136     (context->messages) &&
    137     (context->taggings) &&
    138     (context->invites) &&
    139     (context->files) &&
    140     (context->discourses)
    141   );
    142 
    143   if (context->request_task)
    144     GNUNET_SCHEDULER_cancel(context->request_task);
    145 
    146   if (context->query)
    147     GNUNET_NAMESTORE_cancel(context->query);
    148 
    149   GNUNET_CONTAINER_multishortmap_iterate(
    150     context->timestamps, it_destroy_context_timestamps, NULL
    151   );
    152 
    153   GNUNET_CONTAINER_multihashmap_clear(context->dependencies);
    154   GNUNET_CONTAINER_multihashmap_iterate(
    155     context->messages, it_destroy_context_messages, NULL
    156   );
    157 
    158   GNUNET_CONTAINER_multihashmap_iterate(
    159     context->taggings, it_destroy_context_taggings, NULL
    160   );
    161 
    162   GNUNET_CONTAINER_multihashmap_iterate(
    163     context->invites, it_destroy_context_invites, context
    164   );
    165 
    166   GNUNET_CONTAINER_multishortmap_iterate(
    167     context->discourses, it_destroy_context_discourses, NULL
    168   );
    169 
    170   GNUNET_CONTAINER_multishortmap_destroy(context->member_pointers);
    171 
    172   GNUNET_CONTAINER_multishortmap_destroy(context->timestamps);
    173   GNUNET_CONTAINER_multihashmap_destroy(context->dependencies);
    174   GNUNET_CONTAINER_multihashmap_destroy(context->messages);
    175   GNUNET_CONTAINER_multihashmap_destroy(context->requests);
    176   GNUNET_CONTAINER_multihashmap_destroy(context->taggings);
    177   GNUNET_CONTAINER_multihashmap_destroy(context->invites);
    178   GNUNET_CONTAINER_multihashmap_destroy(context->files);
    179   GNUNET_CONTAINER_multishortmap_destroy(context->discourses);
    180 
    181   if (context->topic)
    182     GNUNET_free(context->topic);
    183 
    184   if (context->nick)
    185     GNUNET_free(context->nick);
    186 
    187   GNUNET_free(context);
    188 }
    189 
    190 void
    191 context_request_message (struct GNUNET_CHAT_Context* context,
    192                          const struct GNUNET_HashCode *hash)
    193 {
    194   GNUNET_assert((context) && (hash));
    195 
    196   if ((!(context->room)) || (GNUNET_YES == context->deleted))
    197     return;
    198 
    199   if ((GNUNET_is_zero(hash)) || 
    200       (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(context->messages, hash)))
    201     return;
    202 
    203   if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put(context->requests,
    204       hash, NULL, GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE))
    205     return;
    206   
    207   if (context->request_task)
    208     return;
    209 
    210   context->request_task = GNUNET_SCHEDULER_add_with_priority(
    211     GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
    212     cb_context_request_messages,
    213     context
    214   );
    215 }
    216 
    217 void
    218 context_update_message (struct GNUNET_CHAT_Context* context,
    219                         const struct GNUNET_HashCode *hash)
    220 {
    221   GNUNET_assert((context) && (hash));
    222 
    223   struct GNUNET_CHAT_Message *message = GNUNET_CONTAINER_multihashmap_get(
    224     context->messages, hash);
    225   
    226   if (!message)
    227     return;
    228 
    229   message->flags |= GNUNET_MESSENGER_FLAG_UPDATE;
    230 
    231   struct GNUNET_CHAT_Handle *handle = context->handle;
    232 
    233   if (!(handle->msg_cb))
    234     return;
    235 
    236   handle->msg_cb(handle->msg_cls, context, message);
    237 }
    238 
    239 void
    240 context_update_room (struct GNUNET_CHAT_Context *context,
    241                      struct GNUNET_MESSENGER_Room *room,
    242                      enum GNUNET_GenericReturnValue record)
    243 {
    244   GNUNET_assert(context);
    245 
    246   if (room == context->room)
    247     return;
    248 
    249   GNUNET_assert(
    250     (context->timestamps) &&
    251     (context->messages) &&
    252     (context->requests) &&
    253     (context->invites) &&
    254     (context->discourses)
    255   );
    256 
    257   GNUNET_CONTAINER_multishortmap_iterate(
    258     context->timestamps, it_destroy_context_timestamps, NULL
    259   );
    260 
    261   GNUNET_CONTAINER_multihashmap_iterate(
    262     context->messages, it_destroy_context_messages, NULL
    263   );
    264 
    265   GNUNET_CONTAINER_multihashmap_iterate(
    266     context->invites, it_destroy_context_invites, context
    267   );
    268 
    269   GNUNET_CONTAINER_multishortmap_iterate(
    270     context->discourses, it_destroy_context_discourses, NULL
    271   );
    272 
    273   GNUNET_CONTAINER_multishortmap_destroy(context->timestamps);
    274   context->timestamps = GNUNET_CONTAINER_multishortmap_create(
    275     initial_map_size_of_room, GNUNET_NO);
    276 
    277   GNUNET_CONTAINER_multihashmap_clear(context->messages);
    278   GNUNET_CONTAINER_multihashmap_clear(context->requests);
    279   GNUNET_CONTAINER_multihashmap_clear(context->invites);
    280   GNUNET_CONTAINER_multihashmap_clear(context->files);
    281 
    282   GNUNET_CONTAINER_multishortmap_destroy(context->discourses);
    283   context->discourses = GNUNET_CONTAINER_multishortmap_create(
    284     initial_map_size_of_room, GNUNET_NO);
    285 
    286   if (context->room)
    287     context_delete(context, GNUNET_YES);
    288 
    289   context->room = room;
    290 
    291   if ((!(context->room)) || (GNUNET_YES != record))
    292     return;
    293 
    294   context_write_records(context);
    295 }
    296 
    297 void
    298 context_update_nick (struct GNUNET_CHAT_Context *context,
    299                      const char *nick)
    300 {
    301   GNUNET_assert(context);
    302 
    303   if (context->nick)
    304     GNUNET_free(context->nick);
    305 
    306   if (nick)
    307     context->nick = GNUNET_strdup(nick);
    308   else
    309     context->nick = NULL;
    310 
    311   if ((!(context->handle)) ||
    312       (GNUNET_YES == context->deleted))
    313     return;
    314 
    315   handle_send_internal_message(
    316     context->handle,
    317     NULL,
    318     context,
    319     GNUNET_CHAT_FLAG_UPDATE_CONTEXT,
    320     NULL,
    321     GNUNET_NO
    322   );
    323 }
    324 
    325 void
    326 context_read_records (struct GNUNET_CHAT_Context *context,
    327                       const char *label,
    328                       unsigned int count,
    329                       const struct GNUNET_GNSRECORD_Data *data)
    330 {
    331   GNUNET_assert((context) && (context->room));
    332 
    333   char *nick = NULL;
    334   char *topic = NULL;
    335   uint32_t flags = 0;
    336 
    337   for (unsigned int i = 0; i < count; i++)
    338   {
    339     if (!(GNUNET_GNSRECORD_RF_SUPPLEMENTAL & data[i].flags))
    340       continue;
    341 
    342     if (GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS == data[i].record_type)
    343     {
    344       if (nick)
    345 	      continue;
    346 
    347       const struct GNUNET_MESSENGER_RoomDetailsRecord *record = data[i].data;
    348 
    349       nick = GNUNET_strndup(record->name, sizeof(record->name));
    350       flags = record->flags;
    351     }
    352 
    353     if (GNUNET_DNSPARSER_TYPE_TXT == data[i].record_type)
    354     {
    355       if (topic)
    356 	      continue;
    357 
    358       topic = GNUNET_strndup(data[i].data, data[i].data_size);
    359     }
    360   }
    361 
    362   context->flags = flags;
    363   context_update_nick(context, nick);
    364 
    365   if (nick)
    366     GNUNET_free(nick);
    367 
    368   const struct GNUNET_HashCode *hash = GNUNET_MESSENGER_room_get_key(
    369     context->room
    370   );
    371 
    372   if (topic)
    373   {
    374     struct GNUNET_HashCode topic_hash;
    375     GNUNET_CRYPTO_hash(topic, strlen(topic), &topic_hash);
    376 
    377     if (0 != GNUNET_CRYPTO_hash_cmp(&topic_hash, hash))
    378     {
    379       GNUNET_free(topic);
    380       topic = NULL;
    381     }
    382   }
    383 
    384   util_set_name_field(topic, &(context->topic));
    385 
    386   if (topic)
    387     GNUNET_free(topic);
    388 
    389   context->type = util_get_context_label_type(label, hash);
    390 }
    391 
    392 void
    393 context_delete_message (struct GNUNET_CHAT_Context *context,
    394                         const struct GNUNET_CHAT_Message *message)
    395 {
    396   GNUNET_assert((context) && (message));
    397 
    398   if (GNUNET_YES != message_has_msg(message))
    399     return;
    400 
    401   struct GNUNET_CHAT_Handle *handle = context->handle;
    402 
    403   switch (message->msg->header.kind)
    404   {
    405     case GNUNET_MESSENGER_KIND_INVITE:
    406     {
    407       struct GNUNET_CHAT_Invitation *invite = GNUNET_CONTAINER_multihashmap_get(
    408         context->invites, &(message->hash)
    409       );
    410 
    411       if (! invite)
    412         break;
    413 
    414       GNUNET_CONTAINER_multihashmap_remove(
    415         handle->invitations, &(invite->key.hash), invite);
    416 
    417       if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove(
    418         context->invites, &(message->hash), invite))
    419         invitation_destroy(invite);
    420       
    421       break;
    422     }
    423     case GNUNET_MESSENGER_KIND_FILE:
    424     {
    425       if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_contains(context->files, &(message->hash)))
    426         break;
    427 
    428       GNUNET_CONTAINER_multihashmap_remove_all(context->files, &(message->hash));
    429       break;
    430     }
    431     case GNUNET_MESSENGER_KIND_TAG:
    432     {
    433       struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get(
    434         context->taggings,
    435         &(message->msg->body.tag.hash)
    436       );
    437 
    438       if (!tagging)
    439         break;
    440 
    441       internal_tagging_remove(tagging, message);
    442       break;
    443     }
    444     default:
    445       break;
    446   }
    447 }
    448 
    449 void
    450 context_write_records (struct GNUNET_CHAT_Context *context)
    451 {
    452   GNUNET_assert((context) && (context->handle) && (context->room));
    453 
    454   const struct GNUNET_CRYPTO_BlindablePrivateKey *zone = handle_get_key(
    455     context->handle
    456   );
    457 
    458   if (!zone)
    459     return;
    460 
    461   const struct GNUNET_HashCode *hash = GNUNET_MESSENGER_room_get_key(
    462     context->room
    463   );
    464 
    465   struct GNUNET_TIME_Absolute expiration = GNUNET_TIME_absolute_get_forever_();
    466 
    467   struct GNUNET_MESSENGER_RoomEntryRecord room_entry;
    468   GNUNET_CRYPTO_get_peer_identity(context->handle->cfg, &(room_entry.door));
    469 
    470   GNUNET_memcpy(
    471     &(room_entry.key),
    472     hash,
    473     sizeof(room_entry.key)
    474   );
    475 
    476   struct GNUNET_MESSENGER_RoomDetailsRecord room_details;
    477   memset(room_details.name, 0, sizeof(room_details.name));
    478   
    479   const char *topic = context->topic;
    480 
    481   if (topic)
    482   {
    483     struct GNUNET_HashCode topic_hash;
    484     GNUNET_CRYPTO_hash(topic, strlen(topic), &topic_hash);
    485 
    486     if (0 != GNUNET_CRYPTO_hash_cmp(&topic_hash, hash))
    487       topic = NULL;
    488   }
    489 
    490   char *label;
    491   util_get_context_label(context->type, hash, &label);
    492 
    493   unsigned int count = 0;
    494   struct GNUNET_GNSRECORD_Data data [3];
    495 
    496   if (GNUNET_YES == context->deleted)
    497     goto skip_record_data;
    498 
    499   data[count].record_type = GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_ENTRY;
    500   data[count].data = &room_entry;
    501   data[count].data_size = sizeof(room_entry);
    502   data[count].expiration_time = expiration.abs_value_us;
    503   data[count].flags = GNUNET_GNSRECORD_RF_PRIVATE;
    504   count++;
    505 
    506   if (context->nick)
    507   {
    508     size_t name_len = strlen(context->nick);
    509     if (name_len >= sizeof(room_details.name))
    510       name_len = sizeof(room_details.name) - 1;
    511 
    512     GNUNET_memcpy(room_details.name, context->nick, name_len);
    513     room_details.name[name_len] = '\0';
    514   }
    515 
    516   if ((context->nick) || (context->flags != 0))
    517   {
    518     room_details.flags = context->flags;
    519 
    520     data[count].record_type = GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS;
    521     data[count].data = &room_details;
    522     data[count].data_size = sizeof(room_details);
    523     data[count].expiration_time = expiration.abs_value_us;
    524     data[count].flags = (
    525       GNUNET_GNSRECORD_RF_PRIVATE |
    526       GNUNET_GNSRECORD_RF_SUPPLEMENTAL
    527     );
    528 
    529     count++;
    530   }
    531 
    532   if (topic)
    533   {
    534     data[count].record_type = GNUNET_DNSPARSER_TYPE_TXT;
    535     data[count].data = topic;
    536     data[count].data_size = strlen(topic);
    537     data[count].expiration_time = expiration.abs_value_us;
    538     data[count].flags = (
    539       GNUNET_GNSRECORD_RF_PRIVATE |
    540       GNUNET_GNSRECORD_RF_SUPPLEMENTAL
    541     );
    542 
    543     count++;
    544   }
    545 
    546 skip_record_data:
    547   if (context->query)
    548     GNUNET_NAMESTORE_cancel(context->query);
    549 
    550   context->query = GNUNET_NAMESTORE_record_set_store(
    551     context->handle->namestore,
    552     zone,
    553     label,
    554     count,
    555     data,
    556     cont_context_write_records,
    557     context
    558   );
    559 
    560   GNUNET_free(label);
    561 }
    562 
    563 void
    564 context_delete (struct GNUNET_CHAT_Context *context,
    565                 enum GNUNET_GenericReturnValue exit)
    566 {
    567   GNUNET_assert((context) && (context->room));
    568 
    569   context->deleted = GNUNET_YES;
    570   context_write_records(context);
    571 
    572   if (GNUNET_YES != exit)
    573     return;
    574 
    575   if (context->request_task)
    576   {
    577     GNUNET_SCHEDULER_cancel(context->request_task);
    578     context->request_task = NULL;
    579   }
    580 
    581   GNUNET_MESSENGER_close_room(context->room);
    582 }