libgnunetchat

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

gnunet_messenger_uml.c (13519B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2024--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_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   char *secret_value;
     66   int public_room;
     67   int ignore_targets;
     68   int ignore_epochs;
     69   int simplify_merges;
     70 
     71   bool quit;
     72 };
     73 
     74 static void
     75 idle (void *cls)
     76 {
     77   struct GNUNET_MESSENGER_Tool *tool = cls;
     78 
     79   tool->task = NULL;
     80   tool->quit = true;
     81 
     82   if (tool->handle)
     83     GNUNET_MESSENGER_disconnect(tool->handle);
     84 
     85   if (tool->lookup)
     86     GNUNET_IDENTITY_ego_lookup_cancel(tool->lookup);
     87 
     88   while (tool->head)
     89   {
     90     struct GNUNET_MESSENGER_Link *link = tool->head;
     91 
     92     const struct GNUNET_HashCode *hash = &(link->hash);
     93     const struct GNUNET_HashCode *previous = &(link->previous);
     94 
     95     printf("X%s ", GNUNET_h2s(hash));
     96 
     97     switch (link->type)
     98     {
     99       case GNUNET_MESSENGER_LINK_DOTTED:
    100         printf("..> ");
    101         break;
    102       case GNUNET_MESSENGER_LINK_COMPOSITION:
    103         printf("*-- ");
    104         break;
    105       default:
    106         printf("--> ");
    107         break;
    108     }
    109     
    110     printf(
    111       "X%s\n",
    112       GNUNET_h2s(previous)
    113     );
    114 
    115     GNUNET_CONTAINER_DLL_remove(tool->head, tool->tail, link);
    116     GNUNET_free(link);
    117   }
    118 }
    119 
    120 static void
    121 add_link (struct GNUNET_MESSENGER_Tool *tool,
    122           const struct GNUNET_HashCode *hash,
    123           const struct GNUNET_HashCode *previous,
    124           enum GNUNET_MESSENGER_LinkType type)
    125 {
    126   size_t i;
    127   for (i = 0; i < sizeof(*previous); i++)
    128     if (((unsigned char*) previous)[i] != 0)
    129       break;
    130   
    131   if (i == sizeof(*previous))
    132     return;
    133 
    134   struct GNUNET_MESSENGER_Link *link = GNUNET_new(
    135     struct GNUNET_MESSENGER_Link
    136   );
    137 
    138   GNUNET_memcpy(&(link->hash), hash, sizeof(*hash));
    139   GNUNET_memcpy(&(link->previous), previous, sizeof(*previous));
    140 
    141   link->type = type;
    142 
    143   GNUNET_CONTAINER_DLL_insert(tool->head, tool->tail, link);
    144 }
    145 
    146 static void
    147 message_callback (void *cls,
    148                   struct GNUNET_MESSENGER_Room *room,
    149                   const struct GNUNET_MESSENGER_Contact *sender,
    150                   const struct GNUNET_MESSENGER_Contact *recipient,
    151                   const struct GNUNET_MESSENGER_Message *message,
    152                   const struct GNUNET_HashCode *hash,
    153                   enum GNUNET_MESSENGER_MessageFlags flags)
    154 {
    155   struct GNUNET_MESSENGER_Tool *tool = cls;
    156 
    157   if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(tool->map, hash))
    158     return;
    159 
    160   if (tool->task)
    161   {
    162     GNUNET_SCHEDULER_cancel(tool->task);
    163     tool->task = NULL;
    164   }
    165 
    166   if ((tool->simplify_merges) &&
    167       (GNUNET_MESSENGER_KIND_MERGE == message->header.kind))
    168   {
    169     printf("<> X%s\n", GNUNET_h2s(hash));
    170     goto print_links;
    171   }
    172 
    173   printf(
    174     "json X%s {",
    175     GNUNET_h2s(hash)
    176   );
    177 
    178   printf(
    179     "\n  \"kind\":\"%s\"",
    180     GNUNET_MESSENGER_name_of_kind(
    181       message->header.kind
    182     )
    183   );
    184 
    185   printf(
    186     ",\n  \"sender_id\":\"%s\"",
    187     GNUNET_sh2s(
    188       &(message->header.sender_id)
    189     )
    190   );
    191 
    192   const struct GNUNET_TIME_Absolute timestamp = GNUNET_TIME_absolute_ntoh(
    193     message->header.timestamp
    194   );
    195 
    196   printf(
    197     ",\n  \"timestamp\":\"%s\"",
    198     GNUNET_STRINGS_absolute_time_to_string(
    199       timestamp
    200     )
    201   );
    202 
    203   if (sender)
    204   {
    205     const unsigned long long sender_address = (unsigned long long) sender;
    206     const char *sender_name = GNUNET_MESSENGER_contact_get_name(
    207       sender
    208     );
    209 
    210     if (sender_name)
    211       printf(
    212         ",\n  \"sender\":[\"0x%llx\",\"%s\"]",
    213         sender_address,
    214         sender_name
    215       );
    216     else
    217       printf(
    218         ",\n  \"sender\":\"0x%llx\"",
    219         sender_address
    220       );
    221   }
    222 
    223   if (recipient)
    224   {
    225     const unsigned long long recipient_address = (unsigned long long) recipient;
    226     const char *recipient_name = GNUNET_MESSENGER_contact_get_name(
    227       recipient
    228     );
    229 
    230     if (recipient_name)
    231       printf(
    232         ",\n  \"recipient\":[\"0x%llx\",\"%s\"]",
    233         recipient_address,
    234         recipient_name
    235       );
    236     else
    237       printf(
    238         ",\n  \"recipient\":\"0x%llx\"",
    239         recipient_address
    240       );
    241   }
    242 
    243   switch (message->header.kind)
    244   {
    245     case GNUNET_MESSENGER_KIND_PEER:
    246       printf(
    247         ",\n  \"peer\":\"%s\"",
    248         GNUNET_i2s(&(message->body.peer.peer))
    249       );
    250       break;
    251     case GNUNET_MESSENGER_KIND_MISS:
    252       printf(
    253         ",\n  \"peer\":\"%s\"",
    254         GNUNET_i2s(&(message->body.miss.peer))
    255       );
    256       break;
    257     case GNUNET_MESSENGER_KIND_TEXT:
    258       printf(
    259         ",\n  \"text\":\"%s\"",
    260         message->body.text.text
    261       );
    262       break;
    263     case GNUNET_MESSENGER_KIND_FILE:
    264       printf(
    265         ",\n  \"file\":[\"%s\",\"%s\"]",
    266         message->body.file.name,
    267         message->body.file.uri
    268       );
    269       break;
    270     case GNUNET_MESSENGER_KIND_TAG:
    271       printf(
    272         ",\n  \"tag\":\"%s\"",
    273         message->body.tag.tag
    274       );
    275       break;
    276     case GNUNET_MESSENGER_KIND_ANNOUNCEMENT:
    277       printf(
    278         ",\n  \"identifier\":\"%s\"",
    279         GNUNET_sh2s(&(message->body.announcement.identifier.hash))
    280       );
    281 
    282       printf(
    283         ",\n  \"group\":\"%s\"",
    284         (message->body.announcement.identifier.code.group_bit? "Y" : "N")
    285       );
    286       break;
    287     case GNUNET_MESSENGER_KIND_SECRET:
    288       printf(
    289         ",\n  \"identifier\":\"%s\"",
    290         GNUNET_sh2s(&(message->body.secret.identifier.hash))
    291       );
    292       break;
    293     case GNUNET_MESSENGER_KIND_REVOLUTION:
    294       printf(
    295         ",\n  \"identifier\":\"%s\"",
    296         GNUNET_sh2s(&(message->body.revolution.identifier.hash))
    297       );
    298       break;
    299     case GNUNET_MESSENGER_KIND_GROUP:
    300       printf(
    301         ",\n  \"identifier\":\"%s\"",
    302         GNUNET_sh2s(&(message->body.group.identifier.hash))
    303       );
    304       break;
    305 	case GNUNET_MESSENGER_KIND_AUTHORIZATION:
    306       printf(
    307         ",\n  \"identifier\":\"%s\"",
    308         GNUNET_sh2s(&(message->body.authorization.identifier.hash))
    309       );
    310 	  break;
    311     default:
    312       break;
    313   }
    314 
    315   printf("\n}\n");
    316 
    317 print_links:
    318   if (GNUNET_MESSENGER_KIND_MERGE == message->header.kind)
    319   {
    320     add_link(tool, hash, &(message->body.merge.previous), GNUNET_MESSENGER_LINK_DEFAULT);
    321 
    322     GNUNET_MESSENGER_get_message(
    323       room,
    324       &(message->body.merge.previous)
    325     );
    326   }
    327 
    328   if (0 == tool->ignore_targets)
    329   {
    330     if (GNUNET_MESSENGER_KIND_REQUEST == message->header.kind)
    331       add_link(tool, hash, &(message->body.request.hash), GNUNET_MESSENGER_LINK_DOTTED);
    332 
    333     if (GNUNET_MESSENGER_KIND_DELETION == message->header.kind)
    334       add_link(tool, hash, &(message->body.deletion.hash), GNUNET_MESSENGER_LINK_DOTTED);
    335 
    336     if (GNUNET_MESSENGER_KIND_TAG == message->header.kind)
    337       add_link(tool, hash, &(message->body.tag.hash), GNUNET_MESSENGER_LINK_DOTTED);
    338 
    339     if (GNUNET_MESSENGER_KIND_APPEAL == message->header.kind)
    340       add_link(tool, hash, &(message->body.appeal.event), GNUNET_MESSENGER_LINK_DOTTED);
    341 
    342     if (GNUNET_MESSENGER_KIND_ACCESS == message->header.kind)
    343       add_link(tool, hash, &(message->body.access.event), GNUNET_MESSENGER_LINK_DOTTED);
    344 
    345     if (GNUNET_MESSENGER_KIND_GROUP == message->header.kind)
    346     {
    347       add_link(tool, hash, &(message->body.group.initiator), GNUNET_MESSENGER_LINK_DOTTED);
    348       add_link(tool, hash, &(message->body.group.partner), GNUNET_MESSENGER_LINK_DOTTED);
    349     }
    350 
    351     if (GNUNET_MESSENGER_KIND_AUTHORIZATION == message->header.kind)
    352       add_link(tool, hash, &(message->body.authorization.event), GNUNET_MESSENGER_LINK_DOTTED);
    353   }
    354 
    355   if (0 == tool->ignore_epochs)
    356   {
    357     if (GNUNET_MESSENGER_KIND_JOIN == message->header.kind)
    358       add_link(tool, hash, &(message->body.join.epoch), GNUNET_MESSENGER_LINK_COMPOSITION);
    359 
    360     if (GNUNET_MESSENGER_KIND_LEAVE == message->header.kind)
    361       add_link(tool, hash, &(message->body.leave.epoch), GNUNET_MESSENGER_LINK_COMPOSITION);
    362 
    363     if ((GNUNET_MESSENGER_KIND_MERGE == message->header.kind) &&
    364         (0 != GNUNET_memcmp(&(message->body.merge.epochs[0]), &(message->body.merge.epochs[1]))))
    365     {
    366       add_link(tool, hash, &(message->body.merge.epochs[0]), GNUNET_MESSENGER_LINK_COMPOSITION);
    367       add_link(tool, hash, &(message->body.merge.epochs[1]), GNUNET_MESSENGER_LINK_COMPOSITION);
    368     }
    369   }
    370 
    371   add_link(tool, hash, &(message->header.previous), GNUNET_MESSENGER_LINK_DEFAULT);
    372 
    373   GNUNET_MESSENGER_get_message(
    374     room,
    375     &(message->header.previous)
    376   );
    377 
    378   GNUNET_CONTAINER_multihashmap_put(
    379     tool->map,
    380     hash,
    381     NULL,
    382     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST
    383   );
    384 
    385   if ((!(tool->quit)) && (!(tool->task)))
    386     tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
    387       GNUNET_TIME_relative_get_second_(),
    388       GNUNET_SCHEDULER_PRIORITY_IDLE,
    389       idle,
    390       tool
    391     );
    392 }
    393 
    394 static void
    395 ego_lookup (void *cls,
    396             struct GNUNET_IDENTITY_Ego *ego)
    397 {
    398   struct GNUNET_MESSENGER_Tool *tool = cls;
    399 
    400   tool->lookup = NULL;
    401 
    402   const struct GNUNET_CRYPTO_BlindablePrivateKey *key;
    403   key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL;
    404 
    405   struct GNUNET_HashCode secret;
    406   if (tool->secret_value)
    407     GNUNET_CRYPTO_hash_from_string (tool->secret_value, &secret);
    408 
    409   tool->handle = GNUNET_MESSENGER_connect(
    410     tool->cfg,
    411     tool->ego_name,
    412     key,
    413     tool->secret_value? &secret : NULL,
    414     message_callback,
    415     tool
    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     NULL,
    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_string(
    495       'S',
    496       "secret",
    497       "SECRET",
    498       "secret for local key storage",
    499       &(tool.secret_value)
    500     ),
    501     GNUNET_GETOPT_option_flag(
    502       'P',
    503       "public",
    504       "disable forward secrecy in public rooms",
    505       &(tool.public_room)
    506     ),
    507     GNUNET_GETOPT_option_flag(
    508       'i',
    509       "ignore-targets",
    510       "ignore indirect connections between messages and their targets",
    511       &(tool.ignore_targets)
    512     ),
    513     GNUNET_GETOPT_option_flag(
    514       'e',
    515       "ignore-epochs",
    516       "ignore indirect connections between epoch messages and their previous epoch",
    517       &(tool.ignore_epochs)
    518     ),
    519     GNUNET_GETOPT_option_flag(
    520       'm',
    521       "simplify-merges",
    522       "simplify merge messages in the message graph",
    523       &(tool.simplify_merges)
    524     ),
    525     GNUNET_GETOPT_OPTION_END
    526   };
    527 
    528   printf("@startuml\n");
    529 
    530   tool.map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO);
    531 
    532   enum GNUNET_GenericReturnValue result = GNUNET_PROGRAM_run(
    533     data,
    534     argc,
    535     argv,
    536     "gnunet_messenger_uml",
    537     gettext_noop("A tool to debug the Messenger service of GNUnet."),
    538     options,
    539     &run,
    540     &tool
    541   );
    542 
    543   GNUNET_CONTAINER_multihashmap_destroy(tool.map);
    544 
    545   printf("@enduml\n");
    546 
    547   return GNUNET_OK == result? 0 : 1;
    548 }