libgnunetchat

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

gnunet_messenger_uml.c (13285B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2024--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_messenger_uml.c
     23  */
     24 
     25 #include <gnunet/gnunet_common.h>
     26 #include <gnunet/gnunet_identity_service.h>
     27 #include <gnunet/gnunet_messenger_service.h>
     28 #include <gnunet/gnunet_scheduler_lib.h>
     29 #include <gnunet/gnunet_time_lib.h>
     30 #include <gnunet/gnunet_util_lib.h>
     31 #include <string.h>
     32 
     33 enum GNUNET_MESSENGER_LinkType
     34 {
     35   GNUNET_MESSENGER_LINK_DEFAULT = 0,
     36   GNUNET_MESSENGER_LINK_DOTTED = 1,
     37   GNUNET_MESSENGER_LINK_COMPOSITION = 2,
     38 };
     39 
     40 struct GNUNET_MESSENGER_Link
     41 {
     42   struct GNUNET_MESSENGER_Link *prev;
     43   struct GNUNET_MESSENGER_Link *next;
     44 
     45   struct GNUNET_HashCode hash;
     46   struct GNUNET_HashCode previous;
     47 
     48   enum GNUNET_MESSENGER_LinkType type;
     49 };
     50 
     51 struct GNUNET_MESSENGER_Tool
     52 {
     53   const struct GNUNET_CONFIGURATION_Handle *cfg;
     54   struct GNUNET_IDENTITY_EgoLookup *lookup;
     55   struct GNUNET_MESSENGER_Handle *handle;
     56   struct GNUNET_SCHEDULER_Task *task;
     57 
     58   struct GNUNET_CONTAINER_MultiHashMap *map;
     59 
     60   struct GNUNET_MESSENGER_Link *head;
     61   struct GNUNET_MESSENGER_Link *tail;
     62 
     63   char *ego_name;
     64   char *room_name;
     65   int public_room;
     66   int ignore_targets;
     67   int ignore_epochs;
     68   int simplify_merges;
     69 
     70   bool quit;
     71 };
     72 
     73 static void
     74 idle (void *cls)
     75 {
     76   struct GNUNET_MESSENGER_Tool *tool = cls;
     77 
     78   tool->task = NULL;
     79   tool->quit = true;
     80 
     81   if (tool->handle)
     82     GNUNET_MESSENGER_disconnect(tool->handle);
     83 
     84   if (tool->lookup)
     85     GNUNET_IDENTITY_ego_lookup_cancel(tool->lookup);
     86 
     87   while (tool->head)
     88   {
     89     struct GNUNET_MESSENGER_Link *link = tool->head;
     90 
     91     const struct GNUNET_HashCode *hash = &(link->hash);
     92     const struct GNUNET_HashCode *previous = &(link->previous);
     93 
     94     printf("X%s ", GNUNET_h2s(hash));
     95 
     96     switch (link->type)
     97     {
     98       case GNUNET_MESSENGER_LINK_DOTTED:
     99         printf("..> ");
    100         break;
    101       case GNUNET_MESSENGER_LINK_COMPOSITION:
    102         printf("*-- ");
    103         break;
    104       default:
    105         printf("--> ");
    106         break;
    107     }
    108     
    109     printf(
    110       "X%s\n",
    111       GNUNET_h2s(previous)
    112     );
    113 
    114     GNUNET_CONTAINER_DLL_remove(tool->head, tool->tail, link);
    115     GNUNET_free(link);
    116   }
    117 }
    118 
    119 static void
    120 add_link (struct GNUNET_MESSENGER_Tool *tool,
    121           const struct GNUNET_HashCode *hash,
    122           const struct GNUNET_HashCode *previous,
    123           enum GNUNET_MESSENGER_LinkType type)
    124 {
    125   size_t i;
    126   for (i = 0; i < sizeof(*previous); i++)
    127     if (((unsigned char*) previous)[i] != 0)
    128       break;
    129   
    130   if (i == sizeof(*previous))
    131     return;
    132 
    133   struct GNUNET_MESSENGER_Link *link = GNUNET_new(
    134     struct GNUNET_MESSENGER_Link
    135   );
    136 
    137   GNUNET_memcpy(&(link->hash), hash, sizeof(*hash));
    138   GNUNET_memcpy(&(link->previous), previous, sizeof(*previous));
    139 
    140   link->type = type;
    141 
    142   GNUNET_CONTAINER_DLL_insert(tool->head, tool->tail, link);
    143 }
    144 
    145 static void
    146 message_callback (void *cls,
    147                   struct GNUNET_MESSENGER_Room *room,
    148                   const struct GNUNET_MESSENGER_Contact *sender,
    149                   const struct GNUNET_MESSENGER_Contact *recipient,
    150                   const struct GNUNET_MESSENGER_Message *message,
    151                   const struct GNUNET_HashCode *hash,
    152                   enum GNUNET_MESSENGER_MessageFlags flags)
    153 {
    154   struct GNUNET_MESSENGER_Tool *tool = cls;
    155 
    156   if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(tool->map, hash))
    157     return;
    158 
    159   if (tool->task)
    160   {
    161     GNUNET_SCHEDULER_cancel(tool->task);
    162     tool->task = NULL;
    163   }
    164 
    165   if ((tool->simplify_merges) &&
    166       (GNUNET_MESSENGER_KIND_MERGE == message->header.kind))
    167   {
    168     printf("<> X%s\n", GNUNET_h2s(hash));
    169     goto print_links;
    170   }
    171 
    172   printf(
    173     "json X%s {",
    174     GNUNET_h2s(hash)
    175   );
    176 
    177   printf(
    178     "\n  \"kind\":\"%s\"",
    179     GNUNET_MESSENGER_name_of_kind(
    180       message->header.kind
    181     )
    182   );
    183 
    184   printf(
    185     ",\n  \"sender_id\":\"%s\"",
    186     GNUNET_sh2s(
    187       &(message->header.sender_id)
    188     )
    189   );
    190 
    191   const struct GNUNET_TIME_Absolute timestamp = GNUNET_TIME_absolute_ntoh(
    192     message->header.timestamp
    193   );
    194 
    195   printf(
    196     ",\n  \"timestamp\":\"%s\"",
    197     GNUNET_STRINGS_absolute_time_to_string(
    198       timestamp
    199     )
    200   );
    201 
    202   if (sender)
    203   {
    204     const unsigned long long sender_address = (unsigned long long) sender;
    205     const char *sender_name = GNUNET_MESSENGER_contact_get_name(
    206       sender
    207     );
    208 
    209     if (sender_name)
    210       printf(
    211         ",\n  \"sender\":[\"0x%llx\",\"%s\"]",
    212         sender_address,
    213         sender_name
    214       );
    215     else
    216       printf(
    217         ",\n  \"sender\":\"0x%llx\"",
    218         sender_address
    219       );
    220   }
    221 
    222   if (recipient)
    223   {
    224     const unsigned long long recipient_address = (unsigned long long) recipient;
    225     const char *recipient_name = GNUNET_MESSENGER_contact_get_name(
    226       recipient
    227     );
    228 
    229     if (recipient_name)
    230       printf(
    231         ",\n  \"recipient\":[\"0x%llx\",\"%s\"]",
    232         recipient_address,
    233         recipient_name
    234       );
    235     else
    236       printf(
    237         ",\n  \"recipient\":\"0x%llx\"",
    238         recipient_address
    239       );
    240   }
    241 
    242   switch (message->header.kind)
    243   {
    244     case GNUNET_MESSENGER_KIND_PEER:
    245       printf(
    246         ",\n  \"peer\":\"%s\"",
    247         GNUNET_i2s(&(message->body.peer.peer))
    248       );
    249       break;
    250     case GNUNET_MESSENGER_KIND_MISS:
    251       printf(
    252         ",\n  \"peer\":\"%s\"",
    253         GNUNET_i2s(&(message->body.miss.peer))
    254       );
    255       break;
    256     case GNUNET_MESSENGER_KIND_TEXT:
    257       printf(
    258         ",\n  \"text\":\"%s\"",
    259         message->body.text.text
    260       );
    261       break;
    262     case GNUNET_MESSENGER_KIND_FILE:
    263       printf(
    264         ",\n  \"file\":[\"%s\",\"%s\"]",
    265         message->body.file.name,
    266         message->body.file.uri
    267       );
    268       break;
    269     case GNUNET_MESSENGER_KIND_TAG:
    270       printf(
    271         ",\n  \"tag\":\"%s\"",
    272         message->body.tag.tag
    273       );
    274       break;
    275     case GNUNET_MESSENGER_KIND_ANNOUNCEMENT:
    276       printf(
    277         ",\n  \"identifier\":\"%s\"",
    278         GNUNET_sh2s(&(message->body.announcement.identifier.hash))
    279       );
    280 
    281       printf(
    282         ",\n  \"group\":\"%s\"",
    283         (message->body.announcement.identifier.code.group_bit? "Y" : "N")
    284       );
    285       break;
    286     case GNUNET_MESSENGER_KIND_SECRET:
    287       printf(
    288         ",\n  \"identifier\":\"%s\"",
    289         GNUNET_sh2s(&(message->body.secret.identifier.hash))
    290       );
    291       break;
    292     case GNUNET_MESSENGER_KIND_REVOLUTION:
    293       printf(
    294         ",\n  \"identifier\":\"%s\"",
    295         GNUNET_sh2s(&(message->body.revolution.identifier.hash))
    296       );
    297       break;
    298     case GNUNET_MESSENGER_KIND_GROUP:
    299       printf(
    300         ",\n  \"identifier\":\"%s\"",
    301         GNUNET_sh2s(&(message->body.group.identifier.hash))
    302       );
    303       break;
    304 	case GNUNET_MESSENGER_KIND_AUTHORIZATION:
    305       printf(
    306         ",\n  \"identifier\":\"%s\"",
    307         GNUNET_sh2s(&(message->body.authorization.identifier.hash))
    308       );
    309 	  break;
    310     default:
    311       break;
    312   }
    313 
    314   printf("\n}\n");
    315 
    316 print_links:
    317   if (GNUNET_MESSENGER_KIND_MERGE == message->header.kind)
    318   {
    319     add_link(tool, hash, &(message->body.merge.previous), GNUNET_MESSENGER_LINK_DEFAULT);
    320 
    321     GNUNET_MESSENGER_get_message(
    322       room,
    323       &(message->body.merge.previous)
    324     );
    325   }
    326 
    327   if (0 == tool->ignore_targets)
    328   {
    329     if (GNUNET_MESSENGER_KIND_REQUEST == message->header.kind)
    330       add_link(tool, hash, &(message->body.request.hash), GNUNET_MESSENGER_LINK_DOTTED);
    331 
    332     if (GNUNET_MESSENGER_KIND_DELETION == message->header.kind)
    333       add_link(tool, hash, &(message->body.deletion.hash), GNUNET_MESSENGER_LINK_DOTTED);
    334 
    335     if (GNUNET_MESSENGER_KIND_TAG == message->header.kind)
    336       add_link(tool, hash, &(message->body.tag.hash), GNUNET_MESSENGER_LINK_DOTTED);
    337 
    338     if (GNUNET_MESSENGER_KIND_APPEAL == message->header.kind)
    339       add_link(tool, hash, &(message->body.appeal.event), GNUNET_MESSENGER_LINK_DOTTED);
    340 
    341     if (GNUNET_MESSENGER_KIND_ACCESS == message->header.kind)
    342       add_link(tool, hash, &(message->body.access.event), GNUNET_MESSENGER_LINK_DOTTED);
    343 
    344     if (GNUNET_MESSENGER_KIND_GROUP == message->header.kind)
    345     {
    346       add_link(tool, hash, &(message->body.group.initiator), GNUNET_MESSENGER_LINK_DOTTED);
    347       add_link(tool, hash, &(message->body.group.partner), GNUNET_MESSENGER_LINK_DOTTED);
    348     }
    349 
    350     if (GNUNET_MESSENGER_KIND_AUTHORIZATION == message->header.kind)
    351       add_link(tool, hash, &(message->body.authorization.event), GNUNET_MESSENGER_LINK_DOTTED);
    352   }
    353 
    354   if (0 == tool->ignore_epochs)
    355   {
    356     if (GNUNET_MESSENGER_KIND_JOIN == message->header.kind)
    357       add_link(tool, hash, &(message->body.join.epoch), GNUNET_MESSENGER_LINK_COMPOSITION);
    358 
    359     if (GNUNET_MESSENGER_KIND_LEAVE == message->header.kind)
    360       add_link(tool, hash, &(message->body.leave.epoch), GNUNET_MESSENGER_LINK_COMPOSITION);
    361 
    362     if ((GNUNET_MESSENGER_KIND_MERGE == message->header.kind) &&
    363         (0 != GNUNET_memcmp(&(message->body.merge.epochs[0]), &(message->body.merge.epochs[1]))))
    364     {
    365       add_link(tool, hash, &(message->body.merge.epochs[0]), GNUNET_MESSENGER_LINK_COMPOSITION);
    366       add_link(tool, hash, &(message->body.merge.epochs[1]), GNUNET_MESSENGER_LINK_COMPOSITION);
    367     }
    368   }
    369 
    370   add_link(tool, hash, &(message->header.previous), GNUNET_MESSENGER_LINK_DEFAULT);
    371 
    372   GNUNET_MESSENGER_get_message(
    373     room,
    374     &(message->header.previous)
    375   );
    376 
    377   GNUNET_CONTAINER_multihashmap_put(
    378     tool->map,
    379     hash,
    380     NULL,
    381     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST
    382   );
    383 
    384   if ((!(tool->quit)) && (!(tool->task)))
    385     tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
    386       GNUNET_TIME_relative_get_second_(),
    387       GNUNET_SCHEDULER_PRIORITY_IDLE,
    388       idle,
    389       tool
    390     );
    391 }
    392 
    393 static void
    394 ego_lookup (void *cls,
    395             struct GNUNET_IDENTITY_Ego *ego)
    396 {
    397   struct GNUNET_MESSENGER_Tool *tool = cls;
    398 
    399   tool->lookup = NULL;
    400 
    401   const struct GNUNET_CRYPTO_BlindablePrivateKey *key;
    402   key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL;
    403 
    404   tool->handle = GNUNET_MESSENGER_connect(
    405     tool->cfg,
    406     tool->ego_name,
    407     key,
    408     message_callback,
    409     tool
    410   );
    411 
    412   struct GNUNET_PeerIdentity peer;
    413   GNUNET_CRYPTO_get_peer_identity(
    414     tool->cfg,
    415     &peer
    416   );
    417 
    418   struct GNUNET_HashCode hash;
    419   
    420   if (tool->room_name)
    421     GNUNET_CRYPTO_hash(
    422       tool->room_name,
    423       strlen(tool->room_name),
    424       &hash
    425     );
    426   else
    427     memset(&hash, 0, sizeof(hash));
    428 
    429   union GNUNET_MESSENGER_RoomKey rkey;
    430   GNUNET_MESSENGER_create_room_key(
    431     &rkey,
    432     tool->room_name,
    433     tool->public_room? GNUNET_YES : GNUNET_NO,
    434     GNUNET_YES,
    435     GNUNET_NO
    436   );
    437   
    438   GNUNET_MESSENGER_enter_room(
    439     tool->handle,
    440     &peer,
    441     &rkey
    442   );
    443 }
    444 
    445 static void
    446 run (void *cls,
    447      char* const* args,
    448      const char *cfgfile,
    449      const struct GNUNET_CONFIGURATION_Handle *cfg)
    450 {
    451   struct GNUNET_MESSENGER_Tool *tool = cls;
    452 
    453   tool->cfg = cfg;
    454 
    455   if (!(tool->ego_name))
    456   {
    457     ego_lookup(tool, NULL);
    458     return;
    459   }
    460 
    461   tool->lookup = GNUNET_IDENTITY_ego_lookup(
    462     cfg,
    463     tool->ego_name,
    464     &ego_lookup,
    465     tool
    466   );
    467 }
    468 
    469 int
    470 main (int argc,
    471       char* const* argv)
    472 {
    473   struct GNUNET_MESSENGER_Tool tool;
    474   memset(&tool, 0, sizeof(tool));
    475 
    476   const struct GNUNET_OS_ProjectData *data;
    477   data = GNUNET_OS_project_data_gnunet ();
    478 
    479   struct GNUNET_GETOPT_CommandLineOption options[] = {
    480     GNUNET_GETOPT_option_string(
    481       'e',
    482       "ego",
    483       "IDENTITY_NAME",
    484       "name of identity to read messages with",
    485       &(tool.ego_name)
    486     ),
    487     GNUNET_GETOPT_option_string(
    488       'r',
    489       "room",
    490       "ROOM_NAME",
    491       "name of room to read messages from",
    492       &(tool.room_name)
    493     ),
    494     GNUNET_GETOPT_option_flag(
    495       'P',
    496       "public",
    497       "disable forward secrecy in public rooms",
    498       &(tool.public_room)
    499     ),
    500     GNUNET_GETOPT_option_flag(
    501       'i',
    502       "ignore-targets",
    503       "ignore indirect connections between messages and their targets",
    504       &(tool.ignore_targets)
    505     ),
    506     GNUNET_GETOPT_option_flag(
    507       'e',
    508       "ignore-epochs",
    509       "ignore indirect connections between epoch messages and their previous epoch",
    510       &(tool.ignore_epochs)
    511     ),
    512     GNUNET_GETOPT_option_flag(
    513       'm',
    514       "simplify-merges",
    515       "simplify merge messages in the message graph",
    516       &(tool.simplify_merges)
    517     ),
    518     GNUNET_GETOPT_OPTION_END
    519   };
    520 
    521   printf("@startuml\n");
    522 
    523   tool.map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO);
    524 
    525   enum GNUNET_GenericReturnValue result = GNUNET_PROGRAM_run(
    526     data,
    527     argc,
    528     argv,
    529     "gnunet_messenger_uml",
    530     gettext_noop("A tool to debug the Messenger service of GNUnet."),
    531     options,
    532     &run,
    533     &tool
    534   );
    535 
    536   GNUNET_CONTAINER_multihashmap_destroy(tool.map);
    537 
    538   printf("@enduml\n");
    539 
    540   return GNUNET_OK == result? 0 : 1;
    541 }