libgnunetchat

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

gnunet_chat_context.c (15157B)


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