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 }