libgnunetchat

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

gnunet_messenger_ping.c (19306B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2025--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_ping.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 <stdint.h>
     32 #include <math.h>
     33 #include <stdio.h>
     34 #include <string.h>
     35 
     36 struct GNUNET_MESSENGER_Ping
     37 {
     38   struct GNUNET_HashCode hash;
     39 
     40   struct GNUNET_TIME_Absolute ping_time;
     41   const struct GNUNET_MESSENGER_Contact *sender;
     42 
     43   struct GNUNET_CONTAINER_MultiShortmap *pong_map;
     44 
     45   size_t pong_missing;
     46   size_t traffic;
     47 };
     48 
     49 struct GNUNET_MESSENGER_PingTool
     50 {
     51   const struct GNUNET_CONFIGURATION_Handle *cfg;
     52   struct GNUNET_IDENTITY_EgoLookup *lookup;
     53   struct GNUNET_MESSENGER_Handle *handle;
     54   struct GNUNET_MESSENGER_Room *room;
     55   struct GNUNET_SCHEDULER_Task *hook;
     56   struct GNUNET_SCHEDULER_Task *task;
     57 
     58   struct GNUNET_CONTAINER_MultiHashMap *map;
     59   struct GNUNET_CONTAINER_MultiHashMap *ping_map;
     60   struct GNUNET_MESSENGER_Ping *last_ping;
     61 
     62   char *ego_name;
     63   char *room_name;
     64   char *secret_value;
     65   uint count;
     66   uint timeout;
     67   uint delay;
     68   int public_room;
     69   int auto_pong;
     70   int join_trigger;
     71 
     72   bool permanent;
     73   size_t counter;
     74 };
     75 
     76 static const struct GNUNET_ShortHashCode*
     77 hash_contact (const struct GNUNET_MESSENGER_Contact *contact)
     78 {
     79   static struct GNUNET_ShortHashCode hash;
     80   memset(&hash, 0, sizeof (hash));
     81 
     82   size_t id = GNUNET_MESSENGER_contact_get_id(contact);
     83   GNUNET_memcpy(&hash, &id, sizeof (id));
     84 
     85   return &hash;
     86 }
     87 
     88 static void
     89 finish_ping (struct GNUNET_MESSENGER_PingTool *tool,
     90              struct GNUNET_MESSENGER_Ping *ping,
     91              struct GNUNET_MESSENGER_Room *room);
     92 
     93 static void
     94 cleanup (void *cls)
     95 {
     96   struct GNUNET_MESSENGER_PingTool *tool = cls;
     97 
     98   tool->task = NULL;
     99 
    100   if (tool->last_ping)
    101     finish_ping(tool, tool->last_ping, tool->room);
    102 
    103   if (tool->hook)
    104   {
    105     GNUNET_SCHEDULER_cancel (tool->hook);
    106     tool->hook = NULL;
    107   }
    108 
    109   if (tool->room)
    110   {
    111     GNUNET_MESSENGER_close_room(tool->room);
    112     tool->room = NULL;
    113   }
    114 
    115   if (tool->handle)
    116   {
    117     GNUNET_MESSENGER_disconnect(tool->handle);
    118     tool->handle = NULL;
    119   }
    120 
    121   if (tool->lookup)
    122   {
    123     GNUNET_IDENTITY_ego_lookup_cancel(tool->lookup);
    124     tool->lookup = NULL;
    125   }
    126 }
    127 
    128 static void
    129 shutdown_hook (void *cls)
    130 {
    131   struct GNUNET_MESSENGER_PingTool *tool = cls;
    132 
    133   tool->hook = NULL;
    134   tool->permanent = false;
    135 
    136   if (tool->task)
    137   {
    138     GNUNET_SCHEDULER_cancel(tool->task);
    139     tool->task = NULL;
    140   }
    141 
    142   cleanup(cls);
    143 }
    144 
    145 static void
    146 finish (void *cls)
    147 {
    148   struct GNUNET_MESSENGER_PingTool *tool = cls;
    149 
    150   tool->task = NULL;
    151 
    152   if (tool->room)
    153   {
    154     GNUNET_MESSENGER_close_room(tool->room);
    155     tool->room = NULL;
    156   }
    157 }
    158 
    159 static void
    160 send_ping (struct GNUNET_MESSENGER_PingTool *tool,
    161            struct GNUNET_MESSENGER_Room *room)
    162 {
    163   struct GNUNET_MESSENGER_Message message;
    164   message.header.kind = GNUNET_MESSENGER_KIND_TEXT;
    165   message.body.text.text = NULL;
    166 
    167   GNUNET_MESSENGER_send_message(room, &message, NULL);
    168   tool->counter++;
    169 }
    170 
    171 static void
    172 send_pong (struct GNUNET_MESSENGER_PingTool *tool,
    173            struct GNUNET_MESSENGER_Room *room,
    174            const struct GNUNET_HashCode *hash,
    175            const struct GNUNET_TIME_Absolute timestamp)
    176 {
    177   struct GNUNET_MESSENGER_Message message;
    178   message.header.kind = GNUNET_MESSENGER_KIND_TAG;
    179   message.body.tag.tag = NULL;
    180 
    181   GNUNET_memcpy(&(message.body.tag.hash), hash, sizeof(*hash));
    182 
    183   const struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference(
    184     timestamp, GNUNET_TIME_absolute_get());
    185 
    186   printf("%s as response to %s from: time=%.3f ms\n",
    187     GNUNET_MESSENGER_name_of_kind(message.header.kind),
    188     GNUNET_h2s(hash),
    189     ((float) difference.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us);
    190 
    191   GNUNET_MESSENGER_send_message(room, &message, NULL);
    192   tool->counter++;
    193 
    194   if ((!(tool->permanent)) && (tool->counter >= tool->count))
    195   {
    196     if (tool->task)
    197       GNUNET_SCHEDULER_cancel(tool->task);
    198 
    199     tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
    200       GNUNET_TIME_relative_get_second_(),
    201       GNUNET_SCHEDULER_PRIORITY_IDLE,
    202       finish,
    203       tool);
    204   }
    205 }
    206 
    207 static void
    208 delay_ping (void *cls)
    209 {
    210   struct GNUNET_MESSENGER_PingTool *tool = cls;
    211 
    212   tool->task = NULL;
    213 
    214   if (tool->join_trigger)
    215     return;
    216 
    217   send_ping(tool, tool->room);
    218 }
    219 
    220 static void
    221 finish_ping (struct GNUNET_MESSENGER_PingTool *tool,
    222              struct GNUNET_MESSENGER_Ping *ping,
    223              struct GNUNET_MESSENGER_Room *room)
    224 {
    225   const size_t recipients = GNUNET_CONTAINER_multishortmap_size(ping->pong_map);
    226   const size_t loss_rate = recipients? 100 * ping->pong_missing / recipients : 100;
    227   const struct GNUNET_TIME_Relative delta = GNUNET_TIME_absolute_get_difference(
    228     ping->ping_time, GNUNET_TIME_absolute_get());
    229   
    230   printf("--- %s ping statistics ---\n", GNUNET_h2s(&(ping->hash)));
    231 
    232   struct GNUNET_TIME_Relative min = GNUNET_TIME_relative_get_forever_();
    233   struct GNUNET_TIME_Relative avg = GNUNET_TIME_relative_get_zero_();
    234   struct GNUNET_TIME_Relative max = GNUNET_TIME_relative_get_zero_();
    235   struct GNUNET_TIME_Relative mdev = GNUNET_TIME_relative_get_zero_();
    236 
    237   struct GNUNET_CONTAINER_MultiShortmapIterator *iter;
    238   const void *value;
    239 
    240   iter = GNUNET_CONTAINER_multishortmap_iterator_create(ping->pong_map);
    241 
    242   while (GNUNET_NO != GNUNET_CONTAINER_multishortmap_iterator_next(iter, NULL, &value))
    243   {
    244     if (!value)
    245       continue;
    246 
    247     const struct GNUNET_TIME_Absolute *time = value;
    248     struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference(
    249       ping->ping_time, *time);
    250     
    251     if (GNUNET_TIME_relative_cmp(difference, <, min))
    252       min = difference;
    253     if (GNUNET_TIME_relative_cmp(difference, >, max))
    254       max = difference;
    255 
    256     avg = GNUNET_TIME_relative_add(avg, difference);
    257   }
    258 
    259   GNUNET_CONTAINER_multishortmap_iterator_destroy(iter);
    260 
    261   if (recipients > ping->pong_missing)
    262     avg = GNUNET_TIME_relative_divide(avg, recipients - ping->pong_missing);
    263 
    264   iter = GNUNET_CONTAINER_multishortmap_iterator_create(ping->pong_map);
    265 
    266   while (GNUNET_NO != GNUNET_CONTAINER_multishortmap_iterator_next(iter, NULL, &value))
    267   {
    268     if (!value)
    269       continue;
    270 
    271     const struct GNUNET_TIME_Absolute *time = value;
    272     struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference(
    273       ping->ping_time, *time);
    274     
    275     difference = GNUNET_TIME_relative_subtract(difference, avg);
    276     difference = GNUNET_TIME_relative_saturating_multiply(difference,
    277       difference.rel_value_us);
    278     
    279     mdev = GNUNET_TIME_relative_add(mdev, difference);
    280   }
    281 
    282   GNUNET_CONTAINER_multishortmap_iterator_destroy(iter);
    283 
    284   if (recipients > ping->pong_missing)
    285     mdev = GNUNET_TIME_relative_divide(mdev, recipients - ping->pong_missing);
    286 
    287   mdev.rel_value_us = (uint64_t) sqrt(mdev.rel_value_us);
    288   
    289   printf("%lu messages exchanged, %lu recipients, %lu%% message loss, time %.3fms\n",
    290     ping->traffic, recipients, loss_rate, ((float) delta.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us);
    291   
    292   if (recipients > 0)
    293     printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n\n",
    294       ((float) min.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us,
    295       ((float) avg.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us,
    296       ((float) max.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us,
    297       ((float) mdev.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us);
    298   
    299   if (ping == tool->last_ping)
    300     tool->last_ping = NULL;
    301 
    302   if (tool->task)
    303     GNUNET_SCHEDULER_cancel(tool->task);
    304   
    305   if ((tool->permanent) || (tool->counter < tool->count))
    306     tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
    307       GNUNET_TIME_relative_multiply(GNUNET_TIME_relative_get_second_(), tool->delay),
    308       GNUNET_SCHEDULER_PRIORITY_IDLE,
    309       delay_ping,
    310       tool);
    311   else
    312     tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
    313       GNUNET_TIME_relative_get_second_(),
    314       GNUNET_SCHEDULER_PRIORITY_IDLE,
    315       finish,
    316       tool);
    317 }
    318 
    319 static enum GNUNET_GenericReturnValue
    320 member_callback (void *cls,
    321                  struct GNUNET_MESSENGER_Room *room,
    322                  const struct GNUNET_MESSENGER_Contact *contact)
    323 {
    324   struct GNUNET_MESSENGER_Ping *ping = cls;
    325 
    326   if (contact == ping->sender)
    327     return GNUNET_YES;
    328 
    329   GNUNET_CONTAINER_multishortmap_put(ping->pong_map, hash_contact (contact), NULL,
    330                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
    331   
    332   return GNUNET_YES;
    333 }
    334 
    335 static void
    336 message_callback (void *cls,
    337                   struct GNUNET_MESSENGER_Room *room,
    338                   const struct GNUNET_MESSENGER_Contact *sender,
    339                   const struct GNUNET_MESSENGER_Contact *recipient,
    340                   const struct GNUNET_MESSENGER_Message *message,
    341                   const struct GNUNET_HashCode *hash,
    342                   enum GNUNET_MESSENGER_MessageFlags flags)
    343 {
    344   struct GNUNET_MESSENGER_PingTool *tool = cls;
    345 
    346   if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_contains(tool->map, hash))
    347   {
    348     struct GNUNET_HashCode *copy = GNUNET_malloc(sizeof(struct GNUNET_HashCode) * 2);
    349     GNUNET_memcpy(copy, &(message->header.previous), sizeof (*copy));
    350 
    351     if (GNUNET_MESSENGER_KIND_MERGE == message->header.kind)
    352       GNUNET_memcpy(copy + 1, &(message->body.merge.previous), sizeof (*copy));
    353     else
    354       GNUNET_memcpy(copy + 1, &(message->header.previous), sizeof (*copy));
    355 
    356     GNUNET_CONTAINER_multihashmap_put(tool->map, hash, copy,
    357                                       GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
    358   }
    359 
    360   if (GNUNET_MESSENGER_FLAG_SENT & flags)
    361   {
    362     switch (message->header.kind)
    363     {
    364       case GNUNET_MESSENGER_KIND_JOIN:
    365       {
    366         if ((!(tool->auto_pong)) && (!(tool->join_trigger)))
    367           send_ping(tool, room);
    368 
    369         break;
    370       }
    371       case GNUNET_MESSENGER_KIND_LEAVE:
    372       {
    373         GNUNET_SCHEDULER_shutdown();
    374         break;
    375       }
    376       case GNUNET_MESSENGER_KIND_TEXT:
    377       {
    378         struct GNUNET_MESSENGER_Ping *ping = GNUNET_new(struct GNUNET_MESSENGER_Ping);
    379 
    380         GNUNET_memcpy(&(ping->hash), hash, sizeof(ping->hash));
    381 
    382         ping->ping_time = GNUNET_TIME_absolute_ntoh(message->header.timestamp);
    383         ping->sender = sender;
    384 
    385         ping->pong_map = GNUNET_CONTAINER_multishortmap_create(8, GNUNET_NO);
    386 
    387         GNUNET_MESSENGER_iterate_members(room, member_callback, ping);
    388 
    389         ping->pong_missing = GNUNET_CONTAINER_multishortmap_size(ping->pong_map);
    390         ping->traffic = 1;
    391 
    392         GNUNET_CONTAINER_multihashmap_put(tool->ping_map, hash, ping,
    393                                           GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
    394         
    395         tool->last_ping = ping;
    396         
    397         if (0 >= ping->pong_missing)
    398           finish_ping (tool, ping, room);
    399 
    400         break;
    401       }
    402       default:
    403         break;
    404     }
    405   }
    406   else if (tool->auto_pong)
    407   {
    408     if (GNUNET_MESSENGER_KIND_TEXT == message->header.kind)
    409       send_pong(tool, room, hash, GNUNET_TIME_absolute_ntoh(message->header.timestamp));
    410   }
    411   else
    412   {
    413     if ((tool->join_trigger) && (GNUNET_MESSENGER_KIND_JOIN == message->header.kind))
    414       send_ping(tool, room);
    415 
    416     if (0 == GNUNET_CONTAINER_multihashmap_size (tool->ping_map))
    417       return;
    418 
    419     struct GNUNET_CONTAINER_MultiHashMapIterator *iter =
    420       GNUNET_CONTAINER_multihashmap_iterator_create(tool->ping_map);
    421     
    422     const void *value;
    423     while (GNUNET_NO != GNUNET_CONTAINER_multihashmap_iterator_next(iter, NULL, &value))
    424     {
    425       struct GNUNET_MESSENGER_Ping *ping = (struct GNUNET_MESSENGER_Ping*) value;
    426 
    427       if (0 >= ping->pong_missing)
    428         continue;
    429 
    430       ping->traffic++;
    431 
    432       if (((GNUNET_MESSENGER_KIND_TAG != message->header.kind) || 
    433            (0 != GNUNET_CRYPTO_hash_cmp(&(message->body.tag.hash), &(ping->hash)))))
    434         continue;
    435       
    436       if (!sender)
    437         continue;
    438 
    439       if (GNUNET_YES != GNUNET_CONTAINER_multishortmap_contains_value(ping->pong_map, hash_contact (sender), NULL))
    440         continue;
    441 
    442       struct GNUNET_TIME_Absolute *time = GNUNET_new(struct GNUNET_TIME_Absolute);
    443       *time = GNUNET_TIME_absolute_ntoh(message->header.timestamp);
    444 
    445       {
    446         struct GNUNET_TIME_Relative difference = GNUNET_TIME_absolute_get_difference(
    447           ping->ping_time, *time);
    448 
    449         printf("%s as response to %s from: sender=%lu time=%.3f ms\n",
    450           GNUNET_MESSENGER_name_of_kind(message->header.kind),
    451           GNUNET_h2s(&(ping->hash)),
    452           GNUNET_MESSENGER_contact_get_id(sender),
    453           ((float) difference.rel_value_us) / GNUNET_TIME_relative_get_millisecond_().rel_value_us);
    454       }
    455 
    456       GNUNET_CONTAINER_multishortmap_put(ping->pong_map, hash_contact (sender), time,
    457                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
    458       
    459       ping->pong_missing--;
    460       if (0 < ping->pong_missing)
    461         continue;
    462 
    463       finish_ping (tool, ping, room);
    464     }
    465     
    466     GNUNET_CONTAINER_multihashmap_iterator_destroy(iter);
    467   }
    468 }
    469 
    470 static void
    471 ego_lookup (void *cls,
    472             struct GNUNET_IDENTITY_Ego *ego)
    473 {
    474   struct GNUNET_MESSENGER_PingTool *tool = cls;
    475 
    476   tool->lookup = NULL;
    477 
    478   const struct GNUNET_CRYPTO_BlindablePrivateKey *key;
    479   key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL;
    480 
    481   struct GNUNET_HashCode secret;
    482   if (tool->secret_value)
    483     GNUNET_CRYPTO_hash_from_string (tool->secret_value, &secret);
    484 
    485   tool->handle = GNUNET_MESSENGER_connect(
    486     tool->cfg,
    487     tool->ego_name,
    488     key,
    489     tool->secret_value? &secret : NULL,
    490     message_callback,
    491     tool
    492   );
    493 
    494   struct GNUNET_PeerIdentity peer;
    495   GNUNET_CRYPTO_get_peer_identity(
    496     tool->cfg,
    497     &peer
    498   );
    499 
    500   if (tool->auto_pong)
    501     printf("PONG ");
    502   else
    503     printf("PING ");
    504 
    505   printf("%s", GNUNET_i2s(&peer));
    506 
    507   union GNUNET_MESSENGER_RoomKey rkey;
    508   if (tool->room_name)
    509   {
    510     printf(":%s", tool->room_name);
    511 
    512     GNUNET_MESSENGER_create_room_key(
    513       &rkey,
    514       tool->room_name,
    515       tool->public_room? GNUNET_YES : GNUNET_NO,
    516       GNUNET_YES,
    517       GNUNET_NO
    518     );
    519   }
    520   else
    521   {
    522     memset(&(rkey.hash), 0, sizeof(rkey.hash));
    523 
    524     rkey.code.public_bit = tool->public_room? 1 : 0;
    525     rkey.code.group_bit = 1;
    526   }
    527 
    528   printf(" (%s): ",
    529     GNUNET_h2s(&(rkey.hash)));
    530   
    531   if (0 == tool->count)
    532   {
    533     printf("infinite\n");
    534     tool->permanent = true;
    535   }
    536   else
    537     printf("%u times\n", tool->count);
    538   
    539   tool->room = GNUNET_MESSENGER_enter_room(
    540     tool->handle,
    541     &peer,
    542     &rkey
    543   );
    544   
    545   if (tool->timeout)
    546     tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
    547       GNUNET_TIME_relative_multiply(
    548         GNUNET_TIME_relative_get_second_(), tool->timeout),
    549       GNUNET_SCHEDULER_PRIORITY_IDLE,
    550       finish,
    551       tool
    552     );
    553 }
    554 
    555 static void
    556 run (void *cls,
    557      char* const* args,
    558      const char *cfgfile,
    559      const struct GNUNET_CONFIGURATION_Handle *cfg)
    560 {
    561   struct GNUNET_MESSENGER_PingTool *tool = cls;
    562 
    563   tool->cfg = cfg;
    564   tool->hook = GNUNET_SCHEDULER_add_shutdown(shutdown_hook, tool);
    565 
    566   if (!(tool->ego_name))
    567   {
    568     ego_lookup(tool, NULL);
    569     return;
    570   }
    571 
    572   tool->lookup = GNUNET_IDENTITY_ego_lookup(
    573     cfg,
    574     tool->ego_name,
    575     &ego_lookup,
    576     tool
    577   );
    578 }
    579 
    580 enum GNUNET_GenericReturnValue
    581 free_map_time (void *cls,
    582                const struct GNUNET_ShortHashCode *key,
    583                void *value)
    584 {
    585   struct GNUNET_TIME_Absolute *time = value;
    586 
    587   if (time)
    588     GNUNET_free(time);
    589 
    590   return GNUNET_YES;
    591 }
    592 
    593 enum GNUNET_GenericReturnValue
    594 free_map_ping (void *cls,
    595                const struct GNUNET_HashCode *key,
    596                void *value)
    597 {
    598   struct GNUNET_MESSENGER_Ping *ping = value;
    599 
    600   GNUNET_CONTAINER_multishortmap_iterate(ping->pong_map, free_map_time, NULL);
    601   GNUNET_CONTAINER_multishortmap_destroy(ping->pong_map);
    602 
    603   GNUNET_free(ping);
    604   return GNUNET_YES;
    605 }
    606 
    607 enum GNUNET_GenericReturnValue
    608 free_map_hashes (void *cls,
    609                  const struct GNUNET_HashCode *key,
    610                  void *value)
    611 {
    612   struct GNUNET_HashCode *hashes = value;
    613   GNUNET_free(hashes);
    614   return GNUNET_YES;
    615 }
    616 
    617 int
    618 main (int argc,
    619       char* const* argv)
    620 {
    621   struct GNUNET_MESSENGER_PingTool tool;
    622   memset(&tool, 0, sizeof(tool));
    623 
    624   const struct GNUNET_OS_ProjectData *data;
    625   data = GNUNET_OS_project_data_gnunet ();
    626 
    627   struct GNUNET_GETOPT_CommandLineOption options[] = {
    628     GNUNET_GETOPT_option_string(
    629       'e',
    630       "ego",
    631       "IDENTITY_NAME",
    632       "name of identity to send/receive messages with",
    633       &(tool.ego_name)
    634     ),
    635     GNUNET_GETOPT_option_string(
    636       'r',
    637       "room",
    638       "ROOM_NAME",
    639       "name of room to read messages from",
    640       &(tool.room_name)
    641     ),
    642     GNUNET_GETOPT_option_string(
    643       'S',
    644       "secret",
    645       "SECRET",
    646       "secret for local key storage",
    647       &(tool.secret_value)
    648     ),
    649     GNUNET_GETOPT_option_uint(
    650       'c',
    651       "count",
    652       "<count>",
    653       "stop after a count of iterations",
    654       &(tool.count)
    655     ),
    656     GNUNET_GETOPT_option_uint(
    657       't',
    658       "timeout",
    659       "<timeout>",
    660       "stop after a timeout in seconds",
    661       &(tool.timeout)
    662     ),
    663     GNUNET_GETOPT_option_uint(
    664       'd',
    665       "delay",
    666       "<delay>",
    667       "delay next iteration in seconds",
    668       &(tool.delay)
    669     ),
    670     GNUNET_GETOPT_option_flag(
    671       'p',
    672       "public",
    673       "disable forward secrecy for public rooms",
    674       &(tool.public_room)
    675     ),
    676     GNUNET_GETOPT_option_flag(
    677       'P',
    678       "pong",
    679       "only send back pong messages after a ping",
    680       &(tool.auto_pong)
    681     ),
    682     GNUNET_GETOPT_option_flag(
    683       'J',
    684       "join-trigger",
    685       "only send a ping message after join events",
    686       &(tool.join_trigger)
    687     ),
    688     GNUNET_GETOPT_OPTION_END
    689   };
    690 
    691   tool.map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO);
    692   tool.ping_map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO);
    693 
    694   enum GNUNET_GenericReturnValue result = GNUNET_PROGRAM_run(
    695     data,
    696     argc,
    697     argv,
    698     "gnunet_messenger_ping",
    699     gettext_noop("A tool to measure latency in the Messenger service of GNUnet."),
    700     options,
    701     &run,
    702     &tool
    703   );
    704 
    705   printf("--- %lu iteration%s done ---\n", tool.counter, tool.counter == 1? "" : "s");
    706 
    707   GNUNET_CONTAINER_multihashmap_iterate(tool.ping_map, free_map_ping, NULL);
    708   GNUNET_CONTAINER_multihashmap_iterate(tool.map, free_map_hashes, NULL);
    709 
    710   GNUNET_CONTAINER_multihashmap_destroy(tool.ping_map);
    711   GNUNET_CONTAINER_multihashmap_destroy(tool.map);
    712 
    713   return GNUNET_OK == result? 0 : 1;
    714 }