libgnunetchat

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

gnunet_messenger_ping.c (18964B)


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