gnunet_messenger_uml.c (13285B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2024--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_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 int public_room; 66 int ignore_targets; 67 int ignore_epochs; 68 int simplify_merges; 69 70 bool quit; 71 }; 72 73 static void 74 idle (void *cls) 75 { 76 struct GNUNET_MESSENGER_Tool *tool = cls; 77 78 tool->task = NULL; 79 tool->quit = true; 80 81 if (tool->handle) 82 GNUNET_MESSENGER_disconnect(tool->handle); 83 84 if (tool->lookup) 85 GNUNET_IDENTITY_ego_lookup_cancel(tool->lookup); 86 87 while (tool->head) 88 { 89 struct GNUNET_MESSENGER_Link *link = tool->head; 90 91 const struct GNUNET_HashCode *hash = &(link->hash); 92 const struct GNUNET_HashCode *previous = &(link->previous); 93 94 printf("X%s ", GNUNET_h2s(hash)); 95 96 switch (link->type) 97 { 98 case GNUNET_MESSENGER_LINK_DOTTED: 99 printf("..> "); 100 break; 101 case GNUNET_MESSENGER_LINK_COMPOSITION: 102 printf("*-- "); 103 break; 104 default: 105 printf("--> "); 106 break; 107 } 108 109 printf( 110 "X%s\n", 111 GNUNET_h2s(previous) 112 ); 113 114 GNUNET_CONTAINER_DLL_remove(tool->head, tool->tail, link); 115 GNUNET_free(link); 116 } 117 } 118 119 static void 120 add_link (struct GNUNET_MESSENGER_Tool *tool, 121 const struct GNUNET_HashCode *hash, 122 const struct GNUNET_HashCode *previous, 123 enum GNUNET_MESSENGER_LinkType type) 124 { 125 size_t i; 126 for (i = 0; i < sizeof(*previous); i++) 127 if (((unsigned char*) previous)[i] != 0) 128 break; 129 130 if (i == sizeof(*previous)) 131 return; 132 133 struct GNUNET_MESSENGER_Link *link = GNUNET_new( 134 struct GNUNET_MESSENGER_Link 135 ); 136 137 GNUNET_memcpy(&(link->hash), hash, sizeof(*hash)); 138 GNUNET_memcpy(&(link->previous), previous, sizeof(*previous)); 139 140 link->type = type; 141 142 GNUNET_CONTAINER_DLL_insert(tool->head, tool->tail, link); 143 } 144 145 static void 146 message_callback (void *cls, 147 struct GNUNET_MESSENGER_Room *room, 148 const struct GNUNET_MESSENGER_Contact *sender, 149 const struct GNUNET_MESSENGER_Contact *recipient, 150 const struct GNUNET_MESSENGER_Message *message, 151 const struct GNUNET_HashCode *hash, 152 enum GNUNET_MESSENGER_MessageFlags flags) 153 { 154 struct GNUNET_MESSENGER_Tool *tool = cls; 155 156 if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(tool->map, hash)) 157 return; 158 159 if (tool->task) 160 { 161 GNUNET_SCHEDULER_cancel(tool->task); 162 tool->task = NULL; 163 } 164 165 if ((tool->simplify_merges) && 166 (GNUNET_MESSENGER_KIND_MERGE == message->header.kind)) 167 { 168 printf("<> X%s\n", GNUNET_h2s(hash)); 169 goto print_links; 170 } 171 172 printf( 173 "json X%s {", 174 GNUNET_h2s(hash) 175 ); 176 177 printf( 178 "\n \"kind\":\"%s\"", 179 GNUNET_MESSENGER_name_of_kind( 180 message->header.kind 181 ) 182 ); 183 184 printf( 185 ",\n \"sender_id\":\"%s\"", 186 GNUNET_sh2s( 187 &(message->header.sender_id) 188 ) 189 ); 190 191 const struct GNUNET_TIME_Absolute timestamp = GNUNET_TIME_absolute_ntoh( 192 message->header.timestamp 193 ); 194 195 printf( 196 ",\n \"timestamp\":\"%s\"", 197 GNUNET_STRINGS_absolute_time_to_string( 198 timestamp 199 ) 200 ); 201 202 if (sender) 203 { 204 const unsigned long long sender_address = (unsigned long long) sender; 205 const char *sender_name = GNUNET_MESSENGER_contact_get_name( 206 sender 207 ); 208 209 if (sender_name) 210 printf( 211 ",\n \"sender\":[\"0x%llx\",\"%s\"]", 212 sender_address, 213 sender_name 214 ); 215 else 216 printf( 217 ",\n \"sender\":\"0x%llx\"", 218 sender_address 219 ); 220 } 221 222 if (recipient) 223 { 224 const unsigned long long recipient_address = (unsigned long long) recipient; 225 const char *recipient_name = GNUNET_MESSENGER_contact_get_name( 226 recipient 227 ); 228 229 if (recipient_name) 230 printf( 231 ",\n \"recipient\":[\"0x%llx\",\"%s\"]", 232 recipient_address, 233 recipient_name 234 ); 235 else 236 printf( 237 ",\n \"recipient\":\"0x%llx\"", 238 recipient_address 239 ); 240 } 241 242 switch (message->header.kind) 243 { 244 case GNUNET_MESSENGER_KIND_PEER: 245 printf( 246 ",\n \"peer\":\"%s\"", 247 GNUNET_i2s(&(message->body.peer.peer)) 248 ); 249 break; 250 case GNUNET_MESSENGER_KIND_MISS: 251 printf( 252 ",\n \"peer\":\"%s\"", 253 GNUNET_i2s(&(message->body.miss.peer)) 254 ); 255 break; 256 case GNUNET_MESSENGER_KIND_TEXT: 257 printf( 258 ",\n \"text\":\"%s\"", 259 message->body.text.text 260 ); 261 break; 262 case GNUNET_MESSENGER_KIND_FILE: 263 printf( 264 ",\n \"file\":[\"%s\",\"%s\"]", 265 message->body.file.name, 266 message->body.file.uri 267 ); 268 break; 269 case GNUNET_MESSENGER_KIND_TAG: 270 printf( 271 ",\n \"tag\":\"%s\"", 272 message->body.tag.tag 273 ); 274 break; 275 case GNUNET_MESSENGER_KIND_ANNOUNCEMENT: 276 printf( 277 ",\n \"identifier\":\"%s\"", 278 GNUNET_sh2s(&(message->body.announcement.identifier.hash)) 279 ); 280 281 printf( 282 ",\n \"group\":\"%s\"", 283 (message->body.announcement.identifier.code.group_bit? "Y" : "N") 284 ); 285 break; 286 case GNUNET_MESSENGER_KIND_SECRET: 287 printf( 288 ",\n \"identifier\":\"%s\"", 289 GNUNET_sh2s(&(message->body.secret.identifier.hash)) 290 ); 291 break; 292 case GNUNET_MESSENGER_KIND_REVOLUTION: 293 printf( 294 ",\n \"identifier\":\"%s\"", 295 GNUNET_sh2s(&(message->body.revolution.identifier.hash)) 296 ); 297 break; 298 case GNUNET_MESSENGER_KIND_GROUP: 299 printf( 300 ",\n \"identifier\":\"%s\"", 301 GNUNET_sh2s(&(message->body.group.identifier.hash)) 302 ); 303 break; 304 case GNUNET_MESSENGER_KIND_AUTHORIZATION: 305 printf( 306 ",\n \"identifier\":\"%s\"", 307 GNUNET_sh2s(&(message->body.authorization.identifier.hash)) 308 ); 309 break; 310 default: 311 break; 312 } 313 314 printf("\n}\n"); 315 316 print_links: 317 if (GNUNET_MESSENGER_KIND_MERGE == message->header.kind) 318 { 319 add_link(tool, hash, &(message->body.merge.previous), GNUNET_MESSENGER_LINK_DEFAULT); 320 321 GNUNET_MESSENGER_get_message( 322 room, 323 &(message->body.merge.previous) 324 ); 325 } 326 327 if (0 == tool->ignore_targets) 328 { 329 if (GNUNET_MESSENGER_KIND_REQUEST == message->header.kind) 330 add_link(tool, hash, &(message->body.request.hash), GNUNET_MESSENGER_LINK_DOTTED); 331 332 if (GNUNET_MESSENGER_KIND_DELETION == message->header.kind) 333 add_link(tool, hash, &(message->body.deletion.hash), GNUNET_MESSENGER_LINK_DOTTED); 334 335 if (GNUNET_MESSENGER_KIND_TAG == message->header.kind) 336 add_link(tool, hash, &(message->body.tag.hash), GNUNET_MESSENGER_LINK_DOTTED); 337 338 if (GNUNET_MESSENGER_KIND_APPEAL == message->header.kind) 339 add_link(tool, hash, &(message->body.appeal.event), GNUNET_MESSENGER_LINK_DOTTED); 340 341 if (GNUNET_MESSENGER_KIND_ACCESS == message->header.kind) 342 add_link(tool, hash, &(message->body.access.event), GNUNET_MESSENGER_LINK_DOTTED); 343 344 if (GNUNET_MESSENGER_KIND_GROUP == message->header.kind) 345 { 346 add_link(tool, hash, &(message->body.group.initiator), GNUNET_MESSENGER_LINK_DOTTED); 347 add_link(tool, hash, &(message->body.group.partner), GNUNET_MESSENGER_LINK_DOTTED); 348 } 349 350 if (GNUNET_MESSENGER_KIND_AUTHORIZATION == message->header.kind) 351 add_link(tool, hash, &(message->body.authorization.event), GNUNET_MESSENGER_LINK_DOTTED); 352 } 353 354 if (0 == tool->ignore_epochs) 355 { 356 if (GNUNET_MESSENGER_KIND_JOIN == message->header.kind) 357 add_link(tool, hash, &(message->body.join.epoch), GNUNET_MESSENGER_LINK_COMPOSITION); 358 359 if (GNUNET_MESSENGER_KIND_LEAVE == message->header.kind) 360 add_link(tool, hash, &(message->body.leave.epoch), GNUNET_MESSENGER_LINK_COMPOSITION); 361 362 if ((GNUNET_MESSENGER_KIND_MERGE == message->header.kind) && 363 (0 != GNUNET_memcmp(&(message->body.merge.epochs[0]), &(message->body.merge.epochs[1])))) 364 { 365 add_link(tool, hash, &(message->body.merge.epochs[0]), GNUNET_MESSENGER_LINK_COMPOSITION); 366 add_link(tool, hash, &(message->body.merge.epochs[1]), GNUNET_MESSENGER_LINK_COMPOSITION); 367 } 368 } 369 370 add_link(tool, hash, &(message->header.previous), GNUNET_MESSENGER_LINK_DEFAULT); 371 372 GNUNET_MESSENGER_get_message( 373 room, 374 &(message->header.previous) 375 ); 376 377 GNUNET_CONTAINER_multihashmap_put( 378 tool->map, 379 hash, 380 NULL, 381 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST 382 ); 383 384 if ((!(tool->quit)) && (!(tool->task))) 385 tool->task = GNUNET_SCHEDULER_add_delayed_with_priority( 386 GNUNET_TIME_relative_get_second_(), 387 GNUNET_SCHEDULER_PRIORITY_IDLE, 388 idle, 389 tool 390 ); 391 } 392 393 static void 394 ego_lookup (void *cls, 395 struct GNUNET_IDENTITY_Ego *ego) 396 { 397 struct GNUNET_MESSENGER_Tool *tool = cls; 398 399 tool->lookup = NULL; 400 401 const struct GNUNET_CRYPTO_BlindablePrivateKey *key; 402 key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL; 403 404 tool->handle = GNUNET_MESSENGER_connect( 405 tool->cfg, 406 tool->ego_name, 407 key, 408 message_callback, 409 tool 410 ); 411 412 struct GNUNET_PeerIdentity peer; 413 GNUNET_CRYPTO_get_peer_identity( 414 tool->cfg, 415 &peer 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 &peer, 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_flag( 495 'P', 496 "public", 497 "disable forward secrecy in public rooms", 498 &(tool.public_room) 499 ), 500 GNUNET_GETOPT_option_flag( 501 'i', 502 "ignore-targets", 503 "ignore indirect connections between messages and their targets", 504 &(tool.ignore_targets) 505 ), 506 GNUNET_GETOPT_option_flag( 507 'e', 508 "ignore-epochs", 509 "ignore indirect connections between epoch messages and their previous epoch", 510 &(tool.ignore_epochs) 511 ), 512 GNUNET_GETOPT_option_flag( 513 'm', 514 "simplify-merges", 515 "simplify merge messages in the message graph", 516 &(tool.simplify_merges) 517 ), 518 GNUNET_GETOPT_OPTION_END 519 }; 520 521 printf("@startuml\n"); 522 523 tool.map = GNUNET_CONTAINER_multihashmap_create(8, GNUNET_NO); 524 525 enum GNUNET_GenericReturnValue result = GNUNET_PROGRAM_run( 526 data, 527 argc, 528 argv, 529 "gnunet_messenger_uml", 530 gettext_noop("A tool to debug the Messenger service of GNUnet."), 531 options, 532 &run, 533 &tool 534 ); 535 536 GNUNET_CONTAINER_multihashmap_destroy(tool.map); 537 538 printf("@enduml\n"); 539 540 return GNUNET_OK == result? 0 : 1; 541 }