gnunet_messenger_uml.c (13519B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2024--2026 GNUnet e.V. 4 5 GNUnet is free software: you can redistribute it and/or modify it 6 under the terms of the GNU Affero General Public License as published 7 by the Free Software Foundation, either version 3 of the License, 8 or (at your option) any later version. 9 10 GNUnet is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 SPDX-License-Identifier: AGPL3.0-or-later 19 */ 20 /* 21 * @author Tobias Frisch 22 * @file gnunet_messenger_uml.c 23 */ 24 25 #include <gnunet/gnunet_common.h> 26 #include <gnunet/gnunet_identity_service.h> 27 #include <gnunet/gnunet_messenger_service.h> 28 #include <gnunet/gnunet_scheduler_lib.h> 29 #include <gnunet/gnunet_time_lib.h> 30 #include <gnunet/gnunet_util_lib.h> 31 #include <string.h> 32 33 enum GNUNET_MESSENGER_LinkType 34 { 35 GNUNET_MESSENGER_LINK_DEFAULT = 0, 36 GNUNET_MESSENGER_LINK_DOTTED = 1, 37 GNUNET_MESSENGER_LINK_COMPOSITION = 2, 38 }; 39 40 struct GNUNET_MESSENGER_Link 41 { 42 struct GNUNET_MESSENGER_Link *prev; 43 struct GNUNET_MESSENGER_Link *next; 44 45 struct GNUNET_HashCode hash; 46 struct GNUNET_HashCode previous; 47 48 enum GNUNET_MESSENGER_LinkType type; 49 }; 50 51 struct GNUNET_MESSENGER_Tool 52 { 53 const struct GNUNET_CONFIGURATION_Handle *cfg; 54 struct GNUNET_IDENTITY_EgoLookup *lookup; 55 struct GNUNET_MESSENGER_Handle *handle; 56 struct GNUNET_SCHEDULER_Task *task; 57 58 struct GNUNET_CONTAINER_MultiHashMap *map; 59 60 struct GNUNET_MESSENGER_Link *head; 61 struct GNUNET_MESSENGER_Link *tail; 62 63 char *ego_name; 64 char *room_name; 65 char *secret_value; 66 int public_room; 67 int ignore_targets; 68 int ignore_epochs; 69 int simplify_merges; 70 71 bool quit; 72 }; 73 74 static void 75 idle (void *cls) 76 { 77 struct GNUNET_MESSENGER_Tool *tool = cls; 78 79 tool->task = NULL; 80 tool->quit = true; 81 82 if (tool->handle) 83 GNUNET_MESSENGER_disconnect(tool->handle); 84 85 if (tool->lookup) 86 GNUNET_IDENTITY_ego_lookup_cancel(tool->lookup); 87 88 while (tool->head) 89 { 90 struct GNUNET_MESSENGER_Link *link = tool->head; 91 92 const struct GNUNET_HashCode *hash = &(link->hash); 93 const struct GNUNET_HashCode *previous = &(link->previous); 94 95 printf("X%s ", GNUNET_h2s(hash)); 96 97 switch (link->type) 98 { 99 case GNUNET_MESSENGER_LINK_DOTTED: 100 printf("..> "); 101 break; 102 case GNUNET_MESSENGER_LINK_COMPOSITION: 103 printf("*-- "); 104 break; 105 default: 106 printf("--> "); 107 break; 108 } 109 110 printf( 111 "X%s\n", 112 GNUNET_h2s(previous) 113 ); 114 115 GNUNET_CONTAINER_DLL_remove(tool->head, tool->tail, link); 116 GNUNET_free(link); 117 } 118 } 119 120 static void 121 add_link (struct GNUNET_MESSENGER_Tool *tool, 122 const struct GNUNET_HashCode *hash, 123 const struct GNUNET_HashCode *previous, 124 enum GNUNET_MESSENGER_LinkType type) 125 { 126 size_t i; 127 for (i = 0; i < sizeof(*previous); i++) 128 if (((unsigned char*) previous)[i] != 0) 129 break; 130 131 if (i == sizeof(*previous)) 132 return; 133 134 struct GNUNET_MESSENGER_Link *link = GNUNET_new( 135 struct GNUNET_MESSENGER_Link 136 ); 137 138 GNUNET_memcpy(&(link->hash), hash, sizeof(*hash)); 139 GNUNET_memcpy(&(link->previous), previous, sizeof(*previous)); 140 141 link->type = type; 142 143 GNUNET_CONTAINER_DLL_insert(tool->head, tool->tail, link); 144 } 145 146 static void 147 message_callback (void *cls, 148 struct GNUNET_MESSENGER_Room *room, 149 const struct GNUNET_MESSENGER_Contact *sender, 150 const struct GNUNET_MESSENGER_Contact *recipient, 151 const struct GNUNET_MESSENGER_Message *message, 152 const struct GNUNET_HashCode *hash, 153 enum GNUNET_MESSENGER_MessageFlags flags) 154 { 155 struct GNUNET_MESSENGER_Tool *tool = cls; 156 157 if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(tool->map, hash)) 158 return; 159 160 if (tool->task) 161 { 162 GNUNET_SCHEDULER_cancel(tool->task); 163 tool->task = NULL; 164 } 165 166 if ((tool->simplify_merges) && 167 (GNUNET_MESSENGER_KIND_MERGE == message->header.kind)) 168 { 169 printf("<> X%s\n", GNUNET_h2s(hash)); 170 goto print_links; 171 } 172 173 printf( 174 "json X%s {", 175 GNUNET_h2s(hash) 176 ); 177 178 printf( 179 "\n \"kind\":\"%s\"", 180 GNUNET_MESSENGER_name_of_kind( 181 message->header.kind 182 ) 183 ); 184 185 printf( 186 ",\n \"sender_id\":\"%s\"", 187 GNUNET_sh2s( 188 &(message->header.sender_id) 189 ) 190 ); 191 192 const struct GNUNET_TIME_Absolute timestamp = GNUNET_TIME_absolute_ntoh( 193 message->header.timestamp 194 ); 195 196 printf( 197 ",\n \"timestamp\":\"%s\"", 198 GNUNET_STRINGS_absolute_time_to_string( 199 timestamp 200 ) 201 ); 202 203 if (sender) 204 { 205 const unsigned long long sender_address = (unsigned long long) sender; 206 const char *sender_name = GNUNET_MESSENGER_contact_get_name( 207 sender 208 ); 209 210 if (sender_name) 211 printf( 212 ",\n \"sender\":[\"0x%llx\",\"%s\"]", 213 sender_address, 214 sender_name 215 ); 216 else 217 printf( 218 ",\n \"sender\":\"0x%llx\"", 219 sender_address 220 ); 221 } 222 223 if (recipient) 224 { 225 const unsigned long long recipient_address = (unsigned long long) recipient; 226 const char *recipient_name = GNUNET_MESSENGER_contact_get_name( 227 recipient 228 ); 229 230 if (recipient_name) 231 printf( 232 ",\n \"recipient\":[\"0x%llx\",\"%s\"]", 233 recipient_address, 234 recipient_name 235 ); 236 else 237 printf( 238 ",\n \"recipient\":\"0x%llx\"", 239 recipient_address 240 ); 241 } 242 243 switch (message->header.kind) 244 { 245 case GNUNET_MESSENGER_KIND_PEER: 246 printf( 247 ",\n \"peer\":\"%s\"", 248 GNUNET_i2s(&(message->body.peer.peer)) 249 ); 250 break; 251 case GNUNET_MESSENGER_KIND_MISS: 252 printf( 253 ",\n \"peer\":\"%s\"", 254 GNUNET_i2s(&(message->body.miss.peer)) 255 ); 256 break; 257 case GNUNET_MESSENGER_KIND_TEXT: 258 printf( 259 ",\n \"text\":\"%s\"", 260 message->body.text.text 261 ); 262 break; 263 case GNUNET_MESSENGER_KIND_FILE: 264 printf( 265 ",\n \"file\":[\"%s\",\"%s\"]", 266 message->body.file.name, 267 message->body.file.uri 268 ); 269 break; 270 case GNUNET_MESSENGER_KIND_TAG: 271 printf( 272 ",\n \"tag\":\"%s\"", 273 message->body.tag.tag 274 ); 275 break; 276 case GNUNET_MESSENGER_KIND_ANNOUNCEMENT: 277 printf( 278 ",\n \"identifier\":\"%s\"", 279 GNUNET_sh2s(&(message->body.announcement.identifier.hash)) 280 ); 281 282 printf( 283 ",\n \"group\":\"%s\"", 284 (message->body.announcement.identifier.code.group_bit? "Y" : "N") 285 ); 286 break; 287 case GNUNET_MESSENGER_KIND_SECRET: 288 printf( 289 ",\n \"identifier\":\"%s\"", 290 GNUNET_sh2s(&(message->body.secret.identifier.hash)) 291 ); 292 break; 293 case GNUNET_MESSENGER_KIND_REVOLUTION: 294 printf( 295 ",\n \"identifier\":\"%s\"", 296 GNUNET_sh2s(&(message->body.revolution.identifier.hash)) 297 ); 298 break; 299 case GNUNET_MESSENGER_KIND_GROUP: 300 printf( 301 ",\n \"identifier\":\"%s\"", 302 GNUNET_sh2s(&(message->body.group.identifier.hash)) 303 ); 304 break; 305 case GNUNET_MESSENGER_KIND_AUTHORIZATION: 306 printf( 307 ",\n \"identifier\":\"%s\"", 308 GNUNET_sh2s(&(message->body.authorization.identifier.hash)) 309 ); 310 break; 311 default: 312 break; 313 } 314 315 printf("\n}\n"); 316 317 print_links: 318 if (GNUNET_MESSENGER_KIND_MERGE == message->header.kind) 319 { 320 add_link(tool, hash, &(message->body.merge.previous), GNUNET_MESSENGER_LINK_DEFAULT); 321 322 GNUNET_MESSENGER_get_message( 323 room, 324 &(message->body.merge.previous) 325 ); 326 } 327 328 if (0 == tool->ignore_targets) 329 { 330 if (GNUNET_MESSENGER_KIND_REQUEST == message->header.kind) 331 add_link(tool, hash, &(message->body.request.hash), GNUNET_MESSENGER_LINK_DOTTED); 332 333 if (GNUNET_MESSENGER_KIND_DELETION == message->header.kind) 334 add_link(tool, hash, &(message->body.deletion.hash), GNUNET_MESSENGER_LINK_DOTTED); 335 336 if (GNUNET_MESSENGER_KIND_TAG == message->header.kind) 337 add_link(tool, hash, &(message->body.tag.hash), GNUNET_MESSENGER_LINK_DOTTED); 338 339 if (GNUNET_MESSENGER_KIND_APPEAL == message->header.kind) 340 add_link(tool, hash, &(message->body.appeal.event), GNUNET_MESSENGER_LINK_DOTTED); 341 342 if (GNUNET_MESSENGER_KIND_ACCESS == message->header.kind) 343 add_link(tool, hash, &(message->body.access.event), GNUNET_MESSENGER_LINK_DOTTED); 344 345 if (GNUNET_MESSENGER_KIND_GROUP == message->header.kind) 346 { 347 add_link(tool, hash, &(message->body.group.initiator), GNUNET_MESSENGER_LINK_DOTTED); 348 add_link(tool, hash, &(message->body.group.partner), GNUNET_MESSENGER_LINK_DOTTED); 349 } 350 351 if (GNUNET_MESSENGER_KIND_AUTHORIZATION == message->header.kind) 352 add_link(tool, hash, &(message->body.authorization.event), GNUNET_MESSENGER_LINK_DOTTED); 353 } 354 355 if (0 == tool->ignore_epochs) 356 { 357 if (GNUNET_MESSENGER_KIND_JOIN == message->header.kind) 358 add_link(tool, hash, &(message->body.join.epoch), GNUNET_MESSENGER_LINK_COMPOSITION); 359 360 if (GNUNET_MESSENGER_KIND_LEAVE == message->header.kind) 361 add_link(tool, hash, &(message->body.leave.epoch), GNUNET_MESSENGER_LINK_COMPOSITION); 362 363 if ((GNUNET_MESSENGER_KIND_MERGE == message->header.kind) && 364 (0 != GNUNET_memcmp(&(message->body.merge.epochs[0]), &(message->body.merge.epochs[1])))) 365 { 366 add_link(tool, hash, &(message->body.merge.epochs[0]), GNUNET_MESSENGER_LINK_COMPOSITION); 367 add_link(tool, hash, &(message->body.merge.epochs[1]), GNUNET_MESSENGER_LINK_COMPOSITION); 368 } 369 } 370 371 add_link(tool, hash, &(message->header.previous), GNUNET_MESSENGER_LINK_DEFAULT); 372 373 GNUNET_MESSENGER_get_message( 374 room, 375 &(message->header.previous) 376 ); 377 378 GNUNET_CONTAINER_multihashmap_put( 379 tool->map, 380 hash, 381 NULL, 382 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST 383 ); 384 385 if ((!(tool->quit)) && (!(tool->task))) 386 tool->task = GNUNET_SCHEDULER_add_delayed_with_priority( 387 GNUNET_TIME_relative_get_second_(), 388 GNUNET_SCHEDULER_PRIORITY_IDLE, 389 idle, 390 tool 391 ); 392 } 393 394 static void 395 ego_lookup (void *cls, 396 struct GNUNET_IDENTITY_Ego *ego) 397 { 398 struct GNUNET_MESSENGER_Tool *tool = cls; 399 400 tool->lookup = NULL; 401 402 const struct GNUNET_CRYPTO_BlindablePrivateKey *key; 403 key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL; 404 405 struct GNUNET_HashCode secret; 406 if (tool->secret_value) 407 GNUNET_CRYPTO_hash_from_string (tool->secret_value, &secret); 408 409 tool->handle = GNUNET_MESSENGER_connect( 410 tool->cfg, 411 tool->ego_name, 412 key, 413 tool->secret_value? &secret : NULL, 414 message_callback, 415 tool 416 ); 417 418 struct GNUNET_HashCode hash; 419 420 if (tool->room_name) 421 GNUNET_CRYPTO_hash( 422 tool->room_name, 423 strlen(tool->room_name), 424 &hash 425 ); 426 else 427 memset(&hash, 0, sizeof(hash)); 428 429 union GNUNET_MESSENGER_RoomKey rkey; 430 GNUNET_MESSENGER_create_room_key( 431 &rkey, 432 tool->room_name, 433 tool->public_room? GNUNET_YES : GNUNET_NO, 434 GNUNET_YES, 435 GNUNET_NO 436 ); 437 438 GNUNET_MESSENGER_enter_room( 439 tool->handle, 440 NULL, 441 &rkey 442 ); 443 } 444 445 static void 446 run (void *cls, 447 char* const* args, 448 const char *cfgfile, 449 const struct GNUNET_CONFIGURATION_Handle *cfg) 450 { 451 struct GNUNET_MESSENGER_Tool *tool = cls; 452 453 tool->cfg = cfg; 454 455 if (!(tool->ego_name)) 456 { 457 ego_lookup(tool, NULL); 458 return; 459 } 460 461 tool->lookup = GNUNET_IDENTITY_ego_lookup( 462 cfg, 463 tool->ego_name, 464 &ego_lookup, 465 tool 466 ); 467 } 468 469 int 470 main (int argc, 471 char* const* argv) 472 { 473 struct GNUNET_MESSENGER_Tool tool; 474 memset(&tool, 0, sizeof(tool)); 475 476 const struct GNUNET_OS_ProjectData *data; 477 data = GNUNET_OS_project_data_gnunet (); 478 479 struct GNUNET_GETOPT_CommandLineOption options[] = { 480 GNUNET_GETOPT_option_string( 481 'e', 482 "ego", 483 "IDENTITY_NAME", 484 "name of identity to read messages with", 485 &(tool.ego_name) 486 ), 487 GNUNET_GETOPT_option_string( 488 'r', 489 "room", 490 "ROOM_NAME", 491 "name of room to read messages from", 492 &(tool.room_name) 493 ), 494 GNUNET_GETOPT_option_string( 495 'S', 496 "secret", 497 "SECRET", 498 "secret for local key storage", 499 &(tool.secret_value) 500 ), 501 GNUNET_GETOPT_option_flag( 502 'P', 503 "public", 504 "disable forward secrecy in public rooms", 505 &(tool.public_room) 506 ), 507 GNUNET_GETOPT_option_flag( 508 'i', 509 "ignore-targets", 510 "ignore indirect connections between messages and their targets", 511 &(tool.ignore_targets) 512 ), 513 GNUNET_GETOPT_option_flag( 514 'e', 515 "ignore-epochs", 516 "ignore indirect connections between epoch messages and their previous epoch", 517 &(tool.ignore_epochs) 518 ), 519 GNUNET_GETOPT_option_flag( 520 'm', 521 "simplify-merges", 522 "simplify merge messages in the message graph", 523 &(tool.simplify_merges) 524 ), 525 GNUNET_GETOPT_OPTION_END 526 }; 527 528 printf("@startuml\n"); 529 530 tool.map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO); 531 532 enum GNUNET_GenericReturnValue result = GNUNET_PROGRAM_run( 533 data, 534 argc, 535 argv, 536 "gnunet_messenger_uml", 537 gettext_noop("A tool to debug the Messenger service of GNUnet."), 538 options, 539 &run, 540 &tool 541 ); 542 543 GNUNET_CONTAINER_multihashmap_destroy(tool.map); 544 545 printf("@enduml\n"); 546 547 return GNUNET_OK == result? 0 : 1; 548 }