libgnunetchat

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

gnunet_chat_contact.c (12054B)


      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_contact.c
     23  */
     24 
     25 #include "gnunet_chat_contact.h"
     26 #include "gnunet_chat_context.h"
     27 #include "gnunet_chat_handle.h"
     28 #include "gnunet_chat_ticket.h"
     29 
     30 #include "internal/gnunet_chat_tagging.h"
     31 
     32 #include <gnunet/gnunet_common.h>
     33 #include <gnunet/gnunet_messenger_service.h>
     34 #include <gnunet/gnunet_time_lib.h>
     35 #include <gnunet/gnunet_util_lib.h>
     36 
     37 #include "gnunet_chat_contact_intern.c"
     38 
     39 static const unsigned int initial_map_size_of_contact = 8;
     40 
     41 struct GNUNET_CHAT_Contact*
     42 contact_create_from_member (struct GNUNET_CHAT_Handle *handle,
     43 			                      const struct GNUNET_MESSENGER_Contact *member)
     44 {
     45   GNUNET_assert((handle) && (member));
     46 
     47   struct GNUNET_CHAT_Contact* contact = GNUNET_new(struct GNUNET_CHAT_Contact);
     48 
     49   contact->handle = handle;
     50   contact->context = NULL;
     51 
     52   contact->destruction = NULL;
     53 
     54   contact->member = member;
     55   contact->joined = GNUNET_CONTAINER_multihashmap_create(
     56     initial_map_size_of_contact, GNUNET_NO);
     57 
     58   contact->tickets_head = NULL;
     59   contact->tickets_tail = NULL;
     60 
     61   contact->public_key = NULL;
     62   contact->user_pointer = NULL;
     63 
     64   contact->owned = GNUNET_NO;
     65 
     66   contact_update_key (contact);
     67   return contact;
     68 }
     69 
     70 void
     71 contact_update_join (struct GNUNET_CHAT_Contact *contact,
     72                      struct GNUNET_CHAT_Context *context,
     73                      const struct GNUNET_HashCode *hash,
     74                      enum GNUNET_MESSENGER_MessageFlags flags)
     75 {
     76   GNUNET_assert(
     77     (contact) &&
     78     (contact->joined) &&
     79     (context) &&
     80     (hash)
     81   );
     82 
     83   if (!(context->room))
     84     return;
     85 
     86   const enum GNUNET_GenericReturnValue blocked = contact_is_tagged(
     87     contact, context, NULL
     88   );
     89 
     90   const struct GNUNET_HashCode *key = GNUNET_MESSENGER_room_get_key(
     91     context->room
     92   );
     93 
     94   struct GNUNET_HashCode *current = GNUNET_CONTAINER_multihashmap_get(
     95     contact->joined,
     96     key
     97   );
     98 
     99   if (! current)
    100   {
    101     current = GNUNET_new(struct GNUNET_HashCode);
    102 
    103     if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put(
    104       contact->joined, key, current, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST))
    105     {
    106       GNUNET_free(current);
    107       return;
    108     }
    109 
    110     GNUNET_memcpy(current, hash, 
    111       sizeof(struct GNUNET_HashCode));
    112     return;
    113   }
    114   else if (0 == (flags & GNUNET_MESSENGER_FLAG_RECENT))
    115     return;
    116 
    117   if (GNUNET_YES == blocked)
    118     contact_untag(contact, context, NULL);
    119 
    120   GNUNET_memcpy(current, hash, 
    121     sizeof(struct GNUNET_HashCode));
    122   
    123   if (GNUNET_YES == blocked)
    124     contact_tag(contact, context, NULL);
    125 }
    126 
    127 void
    128 contact_leave (struct GNUNET_CHAT_Contact *contact,
    129                struct GNUNET_CHAT_Context *context)
    130 {
    131   GNUNET_assert(
    132     (contact) &&
    133     (contact->joined) &&
    134     (context)
    135   );
    136 
    137   if (!(context->room))
    138     return;
    139 
    140   const struct GNUNET_HashCode *key = GNUNET_MESSENGER_room_get_key(
    141     context->room
    142   );
    143 
    144   struct GNUNET_HashCode *current = GNUNET_CONTAINER_multihashmap_get(
    145     contact->joined,
    146     key
    147   );
    148 
    149   if ((! current) ||
    150       (GNUNET_YES != GNUNET_CONTAINER_multihashmap_remove(contact->joined, key, current)))
    151     return;
    152 
    153   GNUNET_free(current);
    154 }
    155 
    156 void
    157 contact_update_key (struct GNUNET_CHAT_Contact *contact)
    158 {
    159   GNUNET_assert(contact);
    160 
    161   if (contact->public_key)
    162     GNUNET_free(contact->public_key);
    163 
    164   const struct GNUNET_CRYPTO_BlindablePublicKey *pubkey;
    165   pubkey = contact_get_key(contact);
    166 
    167   if (pubkey)
    168     contact->public_key = GNUNET_CRYPTO_blindable_public_key_to_string(pubkey);
    169   else
    170     contact->public_key = NULL;
    171 }
    172 
    173 const struct GNUNET_CRYPTO_BlindablePublicKey*
    174 contact_get_key (const struct GNUNET_CHAT_Contact *contact)
    175 {
    176   GNUNET_assert(contact);
    177 
    178   if (!(contact->member))
    179     return NULL;
    180 
    181   return GNUNET_MESSENGER_contact_get_key(contact->member);
    182 }
    183 
    184 struct GNUNET_CHAT_Context*
    185 contact_find_context (const struct GNUNET_CHAT_Contact *contact,
    186                       enum GNUNET_GenericReturnValue room_required)
    187 {
    188   GNUNET_assert(contact);
    189 
    190   if ((contact->context) &&
    191       ((GNUNET_YES != room_required) || (contact->context->room)))
    192     return contact->context;
    193 
    194   struct GNUNET_CHAT_ContactFindRoom find;
    195   find.member_count = 0;
    196   find.room = NULL;
    197 
    198   GNUNET_MESSENGER_find_rooms(
    199     contact->handle->messenger,
    200     contact->member,
    201     it_contact_find_room,
    202     &find
    203   );
    204 
    205   if (!(find.room))
    206     return NULL;
    207 
    208   struct GNUNET_CHAT_Context *context = GNUNET_CONTAINER_multihashmap_get(
    209     contact->handle->contexts,
    210     GNUNET_MESSENGER_room_get_key(find.room)
    211   );
    212 
    213   if ((GNUNET_YES == room_required) && (!(context->room)))
    214     return NULL;
    215 
    216   return context;
    217 }
    218 
    219 const struct GNUNET_HashCode*
    220 get_contact_join_hash (const struct GNUNET_CHAT_Contact *contact,
    221                        const struct GNUNET_CHAT_Context *context)
    222 {
    223   GNUNET_assert((contact) && (context));
    224 
    225   if (!(context->room))
    226     return NULL;
    227 
    228   return GNUNET_CONTAINER_multihashmap_get(
    229     contact->joined,
    230     GNUNET_MESSENGER_room_get_key(context->room)
    231   );
    232 }
    233 
    234 enum GNUNET_GenericReturnValue
    235 contact_is_tagged (const struct GNUNET_CHAT_Contact *contact,
    236                    const struct GNUNET_CHAT_Context *context,
    237                    const char *tag)
    238 {
    239   GNUNET_assert(
    240     (contact) &&
    241     (contact->joined)
    242   );
    243 
    244   const enum GNUNET_GenericReturnValue general = (
    245     context ? GNUNET_NO : GNUNET_YES
    246   );
    247 
    248   if (context)
    249     goto skip_context_search;
    250 
    251   struct GNUNET_CONTAINER_MultiHashMapIterator *iter;
    252   iter = GNUNET_CONTAINER_multihashmap_iterator_create(
    253     contact->joined
    254   );
    255 
    256   if (iter)
    257   {
    258     struct GNUNET_HashCode key;
    259     const void *value;
    260 
    261     while (! context)
    262     {
    263       if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_iterator_next(
    264           iter, &key, &value))
    265         break;
    266 
    267       context = GNUNET_CONTAINER_multihashmap_get(
    268         contact->handle->contexts, &key);
    269     }
    270 
    271     GNUNET_CONTAINER_multihashmap_iterator_destroy(iter);
    272   }
    273 
    274 skip_context_search:
    275   if (! context)
    276     return GNUNET_NO;
    277 
    278   const struct GNUNET_HashCode *hash = get_contact_join_hash(
    279     contact, context);
    280 
    281   if (! hash)
    282     return (general == GNUNET_YES? 
    283       GNUNET_NO : 
    284       contact_is_tagged(contact, NULL, tag)
    285     );
    286   
    287   const struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get(
    288     context->taggings,
    289     hash
    290   );
    291 
    292   if (! tagging)
    293     return GNUNET_NO;
    294 
    295   struct GNUNET_CHAT_ContactFindTag find;
    296   find.hash = NULL;
    297 
    298   internal_tagging_iterate(
    299     tagging,
    300     GNUNET_NO,
    301     tag,
    302     it_contact_find_tag,
    303     &find
    304   );
    305 
    306   if (find.hash)
    307     return GNUNET_YES;
    308   else
    309     return GNUNET_NO;
    310 }
    311 
    312 void
    313 contact_untag (struct GNUNET_CHAT_Contact *contact,
    314                struct GNUNET_CHAT_Context *context,
    315                const char *tag)
    316 {
    317   GNUNET_assert(
    318     (contact) &&
    319     (contact->joined) &&
    320     (context)
    321   );
    322 
    323   const struct GNUNET_HashCode *hash = get_contact_join_hash(
    324     contact, context);
    325 
    326   if (! hash)
    327     return;
    328 
    329   const struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get(
    330     context->taggings,
    331     hash
    332   );
    333 
    334   if (! tagging)
    335     return;
    336 
    337   struct GNUNET_CHAT_ContactFindTag find;
    338   find.hash = NULL;
    339 
    340   internal_tagging_iterate(
    341     tagging,
    342     GNUNET_NO,
    343     tag,
    344     it_contact_find_tag,
    345     &find
    346   );
    347 
    348   if ((! find.hash) || (! context->room))
    349     return;
    350 
    351   GNUNET_MESSENGER_delete_message(
    352     context->room,
    353     find.hash,
    354     GNUNET_TIME_relative_get_zero_()
    355   );
    356 }
    357 
    358 void
    359 contact_tag (struct GNUNET_CHAT_Contact *contact,
    360              struct GNUNET_CHAT_Context *context,
    361              const char *tag)
    362 {
    363   GNUNET_assert(
    364     (contact) &&
    365     (contact->joined) &&
    366     (context)
    367   );
    368 
    369   const struct GNUNET_HashCode *hash = get_contact_join_hash(
    370     contact, context);
    371 
    372   if (! hash)
    373     return;
    374 
    375   const struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get(
    376     context->taggings,
    377     hash
    378   );
    379 
    380   if (! tagging)
    381     goto skip_tag_search;
    382 
    383   struct GNUNET_CHAT_ContactFindTag find;
    384   find.hash = NULL;
    385 
    386   internal_tagging_iterate(
    387     tagging,
    388     GNUNET_NO,
    389     tag,
    390     it_contact_find_tag,
    391     &find
    392   );
    393 
    394   if (find.hash)
    395     return;
    396 
    397 skip_tag_search:
    398   if (! context->room)
    399     return;
    400 
    401   char *tag_value = tag? GNUNET_strdup(tag) : NULL;
    402 
    403   struct GNUNET_MESSENGER_Message msg;
    404   memset(&msg, 0, sizeof(msg));
    405 
    406   msg.header.kind = GNUNET_MESSENGER_KIND_TAG;
    407   GNUNET_memcpy(&(msg.body.tag.hash), hash,
    408     sizeof(struct GNUNET_HashCode));
    409   msg.body.tag.tag = tag_value;
    410 
    411   GNUNET_MESSENGER_send_message(
    412     context->room,
    413     &msg,
    414     contact->member
    415   );
    416 
    417   if (tag_value)
    418     GNUNET_free(tag_value);
    419 }
    420 
    421 int
    422 contact_iterate_tags (struct GNUNET_CHAT_Contact *contact,
    423                       struct GNUNET_CHAT_Context *context,
    424                       GNUNET_CHAT_ContactTagCallback callback,
    425                       void *cls)
    426 {
    427   GNUNET_assert((contact) && (contact->joined));
    428 
    429   if (! context)
    430   {
    431     struct GNUNET_CHAT_ContactIterateUniqueTag it;
    432     it.tags = GNUNET_CONTAINER_multihashmap_create(
    433       initial_map_size_of_contact, GNUNET_NO);
    434     it.callback = callback;
    435     it.cls = cls;
    436 
    437     if (! (it.tags))
    438       return GNUNET_SYSERR;
    439 
    440     int result = GNUNET_SYSERR;
    441 
    442     struct GNUNET_CONTAINER_MultiHashMapIterator *iter;
    443     iter = GNUNET_CONTAINER_multihashmap_iterator_create(
    444       contact->joined
    445     );
    446 
    447     if (! iter)
    448       goto free_tags_iteration;
    449 
    450     struct GNUNET_HashCode key;
    451     const void *value;
    452 
    453     while (! context)
    454     {
    455       if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_iterator_next(
    456           iter, &key, &value))
    457         break;
    458 
    459       context = GNUNET_CONTAINER_multihashmap_get(
    460         contact->handle->contexts, &key);
    461       
    462       if (context)
    463         result = contact_iterate_tags(
    464           contact,
    465           context,
    466           it_contact_iterate_unique_tag,
    467           &it
    468         );
    469     }
    470 
    471     GNUNET_CONTAINER_multihashmap_iterator_destroy(iter);
    472 
    473 free_tags_iteration:
    474     GNUNET_CONTAINER_multihashmap_destroy(it.tags);
    475     return result;
    476   }
    477 
    478   const struct GNUNET_HashCode *hash = get_contact_join_hash(
    479     contact, context);
    480 
    481   if (! hash)
    482     return GNUNET_SYSERR;
    483 
    484   const struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get(
    485     context->taggings,
    486     hash
    487   );
    488 
    489   if (! tagging)
    490     return 0;
    491 
    492   struct GNUNET_CHAT_ContactIterateTag it;
    493   it.contact = contact;
    494   it.callback = callback;
    495   it.cls = cls;
    496 
    497   return internal_tagging_iterate(
    498     tagging,
    499     GNUNET_YES,
    500     NULL,
    501     it_contact_iterate_tag,
    502     &it
    503   );
    504 }
    505 
    506 void
    507 contact_destroy (struct GNUNET_CHAT_Contact* contact)
    508 {
    509   GNUNET_assert(contact);
    510 
    511   if (contact->destruction)
    512     GNUNET_SCHEDULER_cancel(contact->destruction);
    513 
    514   struct GNUNET_CHAT_InternalTickets *tickets;
    515   while (contact->tickets_head)
    516   {
    517     tickets = contact->tickets_head;
    518 
    519     GNUNET_CONTAINER_DLL_remove(
    520       contact->tickets_head,
    521       contact->tickets_tail,
    522       tickets
    523     );
    524 
    525     ticket_destroy(tickets->ticket);
    526 
    527     GNUNET_free(tickets);
    528   }
    529 
    530   if (contact->public_key)
    531     GNUNET_free(contact->public_key);
    532 
    533   if (contact->joined)
    534   {
    535     GNUNET_CONTAINER_multihashmap_iterate(
    536       contact->joined, it_free_join_hashes, NULL
    537     );
    538 
    539     GNUNET_CONTAINER_multihashmap_destroy(contact->joined);
    540   }
    541 
    542   if ((contact->context) && (!(contact->context->room)))
    543     context_destroy(contact->context);
    544 
    545   GNUNET_free(contact);
    546 }