libgnunetchat

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

gnunet_messenger_ping.c (19166B)


      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   if (tool->auto_pong)
    495     printf("PONG");
    496   else
    497     printf("PING");
    498 
    499   union GNUNET_MESSENGER_RoomKey rkey;
    500   if (tool->room_name)
    501   {
    502     printf(":%s", tool->room_name);
    503 
    504     GNUNET_MESSENGER_create_room_key(
    505       &rkey,
    506       tool->room_name,
    507       tool->public_room? GNUNET_YES : GNUNET_NO,
    508       GNUNET_YES,
    509       GNUNET_NO
    510     );
    511   }
    512   else
    513   {
    514     memset(&(rkey.hash), 0, sizeof(rkey.hash));
    515 
    516     rkey.code.public_bit = tool->public_room? 1 : 0;
    517     rkey.code.group_bit = 1;
    518   }
    519 
    520   printf(" (%s): ",
    521     GNUNET_h2s(&(rkey.hash)));
    522   
    523   if (0 == tool->count)
    524   {
    525     printf("infinite\n");
    526     tool->permanent = true;
    527   }
    528   else
    529     printf("%u times\n", tool->count);
    530   
    531   tool->room = GNUNET_MESSENGER_enter_room(
    532     tool->handle,
    533     NULL,
    534     &rkey
    535   );
    536   
    537   if (tool->timeout)
    538     tool->task = GNUNET_SCHEDULER_add_delayed_with_priority(
    539       GNUNET_TIME_relative_multiply(
    540         GNUNET_TIME_relative_get_second_(), tool->timeout),
    541       GNUNET_SCHEDULER_PRIORITY_IDLE,
    542       finish,
    543       tool
    544     );
    545 }
    546 
    547 static void
    548 run (void *cls,
    549      char* const* args,
    550      const char *cfgfile,
    551      const struct GNUNET_CONFIGURATION_Handle *cfg)
    552 {
    553   struct GNUNET_MESSENGER_PingTool *tool = cls;
    554 
    555   tool->cfg = cfg;
    556   tool->hook = GNUNET_SCHEDULER_add_shutdown(shutdown_hook, tool);
    557 
    558   if (!(tool->ego_name))
    559   {
    560     ego_lookup(tool, NULL);
    561     return;
    562   }
    563 
    564   tool->lookup = GNUNET_IDENTITY_ego_lookup(
    565     cfg,
    566     tool->ego_name,
    567     &ego_lookup,
    568     tool
    569   );
    570 }
    571 
    572 enum GNUNET_GenericReturnValue
    573 free_map_time (void *cls,
    574                const struct GNUNET_ShortHashCode *key,
    575                void *value)
    576 {
    577   struct GNUNET_TIME_Absolute *time = value;
    578 
    579   if (time)
    580     GNUNET_free(time);
    581 
    582   return GNUNET_YES;
    583 }
    584 
    585 enum GNUNET_GenericReturnValue
    586 free_map_ping (void *cls,
    587                const struct GNUNET_HashCode *key,
    588                void *value)
    589 {
    590   struct GNUNET_MESSENGER_Ping *ping = value;
    591 
    592   GNUNET_CONTAINER_multishortmap_iterate(ping->pong_map, free_map_time, NULL);
    593   GNUNET_CONTAINER_multishortmap_destroy(ping->pong_map);
    594 
    595   GNUNET_free(ping);
    596   return GNUNET_YES;
    597 }
    598 
    599 enum GNUNET_GenericReturnValue
    600 free_map_hashes (void *cls,
    601                  const struct GNUNET_HashCode *key,
    602                  void *value)
    603 {
    604   struct GNUNET_HashCode *hashes = value;
    605   GNUNET_free(hashes);
    606   return GNUNET_YES;
    607 }
    608 
    609 int
    610 main (int argc,
    611       char* const* argv)
    612 {
    613   struct GNUNET_MESSENGER_PingTool tool;
    614   memset(&tool, 0, sizeof(tool));
    615 
    616   const struct GNUNET_OS_ProjectData *data;
    617   data = GNUNET_OS_project_data_gnunet ();
    618 
    619   struct GNUNET_GETOPT_CommandLineOption options[] = {
    620     GNUNET_GETOPT_option_string(
    621       'e',
    622       "ego",
    623       "IDENTITY_NAME",
    624       "name of identity to send/receive messages with",
    625       &(tool.ego_name)
    626     ),
    627     GNUNET_GETOPT_option_string(
    628       'r',
    629       "room",
    630       "ROOM_NAME",
    631       "name of room to read messages from",
    632       &(tool.room_name)
    633     ),
    634     GNUNET_GETOPT_option_string(
    635       'S',
    636       "secret",
    637       "SECRET",
    638       "secret for local key storage",
    639       &(tool.secret_value)
    640     ),
    641     GNUNET_GETOPT_option_uint(
    642       'c',
    643       "count",
    644       "<count>",
    645       "stop after a count of iterations",
    646       &(tool.count)
    647     ),
    648     GNUNET_GETOPT_option_uint(
    649       't',
    650       "timeout",
    651       "<timeout>",
    652       "stop after a timeout in seconds",
    653       &(tool.timeout)
    654     ),
    655     GNUNET_GETOPT_option_uint(
    656       'd',
    657       "delay",
    658       "<delay>",
    659       "delay next iteration in seconds",
    660       &(tool.delay)
    661     ),
    662     GNUNET_GETOPT_option_flag(
    663       'p',
    664       "public",
    665       "disable forward secrecy for public rooms",
    666       &(tool.public_room)
    667     ),
    668     GNUNET_GETOPT_option_flag(
    669       'P',
    670       "pong",
    671       "only send back pong messages after a ping",
    672       &(tool.auto_pong)
    673     ),
    674     GNUNET_GETOPT_option_flag(
    675       'J',
    676       "join-trigger",
    677       "only send a ping message after join events",
    678       &(tool.join_trigger)
    679     ),
    680     GNUNET_GETOPT_OPTION_END
    681   };
    682 
    683   tool.map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO);
    684   tool.ping_map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO);
    685 
    686   enum GNUNET_GenericReturnValue result = GNUNET_PROGRAM_run(
    687     data,
    688     argc,
    689     argv,
    690     "gnunet_messenger_ping",
    691     gettext_noop("A tool to measure latency in the Messenger service of GNUnet."),
    692     options,
    693     &run,
    694     &tool
    695   );
    696 
    697   printf("--- %lu iteration%s done ---\n", tool.counter, tool.counter == 1? "" : "s");
    698 
    699   GNUNET_CONTAINER_multihashmap_iterate(tool.ping_map, free_map_ping, NULL);
    700   GNUNET_CONTAINER_multihashmap_iterate(tool.map, free_map_hashes, NULL);
    701 
    702   GNUNET_CONTAINER_multihashmap_destroy(tool.ping_map);
    703   GNUNET_CONTAINER_multihashmap_destroy(tool.map);
    704 
    705   return GNUNET_OK == result? 0 : 1;
    706 }