libgnunetchat

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

gnunet_chat_contact.c (12353B)


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