gnunet_chat_context.c (15065B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2021--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_chat_context.c 23 */ 24 25 #include "gnunet_chat_context.h" 26 #include "gnunet_chat_file.h" 27 #include "gnunet_chat_handle.h" 28 #include "gnunet_chat_message.h" 29 #include "gnunet_chat_util.h" 30 31 #include "gnunet_chat_context_intern.c" 32 #include <gnunet/gnunet_common.h> 33 #include <gnunet/gnunet_messenger_service.h> 34 #include <gnunet/gnunet_namestore_service.h> 35 #include <gnunet/gnunet_scheduler_lib.h> 36 #include <string.h> 37 38 static const unsigned int initial_map_size_of_room = 8; 39 static const unsigned int initial_map_size_of_contact = 4; 40 41 static void 42 init_new_context (struct GNUNET_CHAT_Context *context, 43 unsigned int initial_map_size) 44 { 45 GNUNET_assert(context); 46 47 context->flags = 0; 48 context->nick = NULL; 49 context->topic = NULL; 50 context->deleted = GNUNET_NO; 51 52 context->request_task = NULL; 53 54 context->timestamps = GNUNET_CONTAINER_multishortmap_create( 55 initial_map_size, GNUNET_NO); 56 context->dependencies = GNUNET_CONTAINER_multihashmap_create( 57 initial_map_size, GNUNET_NO); 58 context->messages = GNUNET_CONTAINER_multihashmap_create( 59 initial_map_size, GNUNET_NO); 60 context->requests = GNUNET_CONTAINER_multihashmap_create( 61 initial_map_size, GNUNET_NO); 62 context->taggings = GNUNET_CONTAINER_multihashmap_create( 63 initial_map_size, GNUNET_NO); 64 context->invites = GNUNET_CONTAINER_multihashmap_create( 65 initial_map_size, GNUNET_NO); 66 context->files = GNUNET_CONTAINER_multihashmap_create( 67 initial_map_size, GNUNET_NO); 68 context->discourses = GNUNET_CONTAINER_multishortmap_create( 69 initial_map_size, GNUNET_NO); 70 71 context->user_pointer = NULL; 72 73 context->member_pointers = GNUNET_CONTAINER_multishortmap_create( 74 initial_map_size, GNUNET_NO); 75 76 context->query = NULL; 77 } 78 79 struct GNUNET_CHAT_Context* 80 context_create_from_room (struct GNUNET_CHAT_Handle *handle, 81 struct GNUNET_MESSENGER_Room *room) 82 { 83 GNUNET_assert((handle) && (room)); 84 85 struct GNUNET_CHAT_Context* context = GNUNET_new(struct GNUNET_CHAT_Context); 86 87 context->handle = handle; 88 context->type = GNUNET_CHAT_CONTEXT_TYPE_UNKNOWN; 89 90 init_new_context(context, initial_map_size_of_room); 91 92 context->room = room; 93 context->contact = NULL; 94 95 union GNUNET_MESSENGER_RoomKey key; 96 GNUNET_memcpy( 97 &(key.hash), 98 GNUNET_MESSENGER_room_get_key(room), 99 sizeof (key.hash) 100 ); 101 102 if (key.code.group_bit) 103 context->type = GNUNET_CHAT_CONTEXT_TYPE_GROUP; 104 else 105 context->type = GNUNET_CHAT_CONTEXT_TYPE_CONTACT; 106 107 return context; 108 } 109 110 struct GNUNET_CHAT_Context* 111 context_create_from_contact (struct GNUNET_CHAT_Handle *handle, 112 const struct GNUNET_MESSENGER_Contact *contact) 113 { 114 GNUNET_assert((handle) && (contact)); 115 116 struct GNUNET_CHAT_Context* context = GNUNET_new(struct GNUNET_CHAT_Context); 117 118 context->handle = handle; 119 context->type = GNUNET_CHAT_CONTEXT_TYPE_CONTACT; 120 121 init_new_context(context, initial_map_size_of_contact); 122 123 context->room = NULL; 124 context->contact = contact; 125 126 return context; 127 } 128 129 void 130 context_destroy (struct GNUNET_CHAT_Context *context) 131 { 132 GNUNET_assert( 133 (context) && 134 (context->timestamps) && 135 (context->dependencies) && 136 (context->messages) && 137 (context->taggings) && 138 (context->invites) && 139 (context->files) && 140 (context->discourses) 141 ); 142 143 if (context->request_task) 144 GNUNET_SCHEDULER_cancel(context->request_task); 145 146 if (context->query) 147 GNUNET_NAMESTORE_cancel(context->query); 148 149 GNUNET_CONTAINER_multishortmap_iterate( 150 context->timestamps, it_destroy_context_timestamps, NULL 151 ); 152 153 GNUNET_CONTAINER_multihashmap_clear(context->dependencies); 154 GNUNET_CONTAINER_multihashmap_iterate( 155 context->messages, it_destroy_context_messages, NULL 156 ); 157 158 GNUNET_CONTAINER_multihashmap_iterate( 159 context->taggings, it_destroy_context_taggings, NULL 160 ); 161 162 GNUNET_CONTAINER_multihashmap_iterate( 163 context->invites, it_destroy_context_invites, context 164 ); 165 166 GNUNET_CONTAINER_multishortmap_iterate( 167 context->discourses, it_destroy_context_discourses, NULL 168 ); 169 170 GNUNET_CONTAINER_multishortmap_destroy(context->member_pointers); 171 172 GNUNET_CONTAINER_multishortmap_destroy(context->timestamps); 173 GNUNET_CONTAINER_multihashmap_destroy(context->dependencies); 174 GNUNET_CONTAINER_multihashmap_destroy(context->messages); 175 GNUNET_CONTAINER_multihashmap_destroy(context->requests); 176 GNUNET_CONTAINER_multihashmap_destroy(context->taggings); 177 GNUNET_CONTAINER_multihashmap_destroy(context->invites); 178 GNUNET_CONTAINER_multihashmap_destroy(context->files); 179 GNUNET_CONTAINER_multishortmap_destroy(context->discourses); 180 181 if (context->topic) 182 GNUNET_free(context->topic); 183 184 if (context->nick) 185 GNUNET_free(context->nick); 186 187 GNUNET_free(context); 188 } 189 190 void 191 context_request_message (struct GNUNET_CHAT_Context* context, 192 const struct GNUNET_HashCode *hash) 193 { 194 GNUNET_assert((context) && (hash)); 195 196 if ((!(context->room)) || (GNUNET_YES == context->deleted)) 197 return; 198 199 if ((GNUNET_is_zero(hash)) || 200 (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(context->messages, hash))) 201 return; 202 203 if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put(context->requests, 204 hash, NULL, GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE)) 205 return; 206 207 if (context->request_task) 208 return; 209 210 context->request_task = GNUNET_SCHEDULER_add_with_priority( 211 GNUNET_SCHEDULER_PRIORITY_BACKGROUND, 212 cb_context_request_messages, 213 context 214 ); 215 } 216 217 void 218 context_update_message (struct GNUNET_CHAT_Context* context, 219 const struct GNUNET_HashCode *hash) 220 { 221 GNUNET_assert((context) && (hash)); 222 223 struct GNUNET_CHAT_Message *message = GNUNET_CONTAINER_multihashmap_get( 224 context->messages, hash); 225 226 if (!message) 227 return; 228 229 message->flags |= GNUNET_MESSENGER_FLAG_UPDATE; 230 231 struct GNUNET_CHAT_Handle *handle = context->handle; 232 233 if (!(handle->msg_cb)) 234 return; 235 236 handle->msg_cb(handle->msg_cls, context, message); 237 } 238 239 void 240 context_update_room (struct GNUNET_CHAT_Context *context, 241 struct GNUNET_MESSENGER_Room *room, 242 enum GNUNET_GenericReturnValue record) 243 { 244 GNUNET_assert(context); 245 246 if (room == context->room) 247 return; 248 249 GNUNET_assert( 250 (context->timestamps) && 251 (context->messages) && 252 (context->requests) && 253 (context->invites) && 254 (context->discourses) 255 ); 256 257 GNUNET_CONTAINER_multishortmap_iterate( 258 context->timestamps, it_destroy_context_timestamps, NULL 259 ); 260 261 GNUNET_CONTAINER_multihashmap_iterate( 262 context->messages, it_destroy_context_messages, NULL 263 ); 264 265 GNUNET_CONTAINER_multihashmap_iterate( 266 context->invites, it_destroy_context_invites, context 267 ); 268 269 GNUNET_CONTAINER_multishortmap_iterate( 270 context->discourses, it_destroy_context_discourses, NULL 271 ); 272 273 GNUNET_CONTAINER_multishortmap_destroy(context->timestamps); 274 context->timestamps = GNUNET_CONTAINER_multishortmap_create( 275 initial_map_size_of_room, GNUNET_NO); 276 277 GNUNET_CONTAINER_multihashmap_clear(context->messages); 278 GNUNET_CONTAINER_multihashmap_clear(context->requests); 279 GNUNET_CONTAINER_multihashmap_clear(context->invites); 280 GNUNET_CONTAINER_multihashmap_clear(context->files); 281 282 GNUNET_CONTAINER_multishortmap_destroy(context->discourses); 283 context->discourses = GNUNET_CONTAINER_multishortmap_create( 284 initial_map_size_of_room, GNUNET_NO); 285 286 if (context->room) 287 context_delete(context, GNUNET_YES); 288 289 context->room = room; 290 291 if ((!(context->room)) || (GNUNET_YES != record)) 292 return; 293 294 context_write_records(context); 295 } 296 297 void 298 context_update_nick (struct GNUNET_CHAT_Context *context, 299 const char *nick) 300 { 301 GNUNET_assert(context); 302 303 if (context->nick) 304 GNUNET_free(context->nick); 305 306 if (nick) 307 context->nick = GNUNET_strdup(nick); 308 else 309 context->nick = NULL; 310 311 if ((!(context->handle)) || 312 (GNUNET_YES == context->deleted)) 313 return; 314 315 handle_send_internal_message( 316 context->handle, 317 NULL, 318 context, 319 GNUNET_CHAT_FLAG_UPDATE_CONTEXT, 320 NULL, 321 GNUNET_NO 322 ); 323 } 324 325 void 326 context_read_records (struct GNUNET_CHAT_Context *context, 327 const char *label, 328 unsigned int count, 329 const struct GNUNET_GNSRECORD_Data *data) 330 { 331 GNUNET_assert((context) && (context->room)); 332 333 char *nick = NULL; 334 char *topic = NULL; 335 uint32_t flags = 0; 336 337 for (unsigned int i = 0; i < count; i++) 338 { 339 if (!(GNUNET_GNSRECORD_RF_SUPPLEMENTAL & data[i].flags)) 340 continue; 341 342 if (GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS == data[i].record_type) 343 { 344 if (nick) 345 continue; 346 347 const struct GNUNET_MESSENGER_RoomDetailsRecord *record = data[i].data; 348 349 nick = GNUNET_strndup(record->name, sizeof(record->name)); 350 flags = record->flags; 351 } 352 353 if (GNUNET_DNSPARSER_TYPE_TXT == data[i].record_type) 354 { 355 if (topic) 356 continue; 357 358 topic = GNUNET_strndup(data[i].data, data[i].data_size); 359 } 360 } 361 362 context->flags = flags; 363 context_update_nick(context, nick); 364 365 if (nick) 366 GNUNET_free(nick); 367 368 const struct GNUNET_HashCode *hash = GNUNET_MESSENGER_room_get_key( 369 context->room 370 ); 371 372 if (topic) 373 { 374 struct GNUNET_HashCode topic_hash; 375 GNUNET_CRYPTO_hash(topic, strlen(topic), &topic_hash); 376 377 if (0 != GNUNET_CRYPTO_hash_cmp(&topic_hash, hash)) 378 { 379 GNUNET_free(topic); 380 topic = NULL; 381 } 382 } 383 384 util_set_name_field(topic, &(context->topic)); 385 386 if (topic) 387 GNUNET_free(topic); 388 389 context->type = util_get_context_label_type(label, hash); 390 } 391 392 void 393 context_delete_message (struct GNUNET_CHAT_Context *context, 394 const struct GNUNET_CHAT_Message *message) 395 { 396 GNUNET_assert((context) && (message)); 397 398 if (GNUNET_YES != message_has_msg(message)) 399 return; 400 401 struct GNUNET_CHAT_Handle *handle = context->handle; 402 403 switch (message->msg->header.kind) 404 { 405 case GNUNET_MESSENGER_KIND_INVITE: 406 { 407 struct GNUNET_CHAT_Invitation *invite = GNUNET_CONTAINER_multihashmap_get( 408 context->invites, &(message->hash) 409 ); 410 411 if (! invite) 412 break; 413 414 GNUNET_CONTAINER_multihashmap_remove( 415 handle->invitations, &(invite->key.hash), invite); 416 417 if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove( 418 context->invites, &(message->hash), invite)) 419 invitation_destroy(invite); 420 421 break; 422 } 423 case GNUNET_MESSENGER_KIND_FILE: 424 { 425 if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_contains(context->files, &(message->hash))) 426 break; 427 428 GNUNET_CONTAINER_multihashmap_remove_all(context->files, &(message->hash)); 429 break; 430 } 431 case GNUNET_MESSENGER_KIND_TAG: 432 { 433 struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get( 434 context->taggings, 435 &(message->msg->body.tag.hash) 436 ); 437 438 if (!tagging) 439 break; 440 441 internal_tagging_remove(tagging, message); 442 break; 443 } 444 default: 445 break; 446 } 447 } 448 449 void 450 context_write_records (struct GNUNET_CHAT_Context *context) 451 { 452 GNUNET_assert((context) && (context->handle) && (context->room)); 453 454 const struct GNUNET_CRYPTO_BlindablePrivateKey *zone = handle_get_key( 455 context->handle 456 ); 457 458 if (!zone) 459 return; 460 461 const struct GNUNET_HashCode *hash = GNUNET_MESSENGER_room_get_key( 462 context->room 463 ); 464 465 struct GNUNET_TIME_Absolute expiration = GNUNET_TIME_absolute_get_forever_(); 466 467 struct GNUNET_MESSENGER_RoomEntryRecord room_entry; 468 GNUNET_CRYPTO_get_peer_identity(context->handle->cfg, &(room_entry.door)); 469 470 GNUNET_memcpy( 471 &(room_entry.key), 472 hash, 473 sizeof(room_entry.key) 474 ); 475 476 struct GNUNET_MESSENGER_RoomDetailsRecord room_details; 477 memset(room_details.name, 0, sizeof(room_details.name)); 478 479 const char *topic = context->topic; 480 481 if (topic) 482 { 483 struct GNUNET_HashCode topic_hash; 484 GNUNET_CRYPTO_hash(topic, strlen(topic), &topic_hash); 485 486 if (0 != GNUNET_CRYPTO_hash_cmp(&topic_hash, hash)) 487 topic = NULL; 488 } 489 490 char *label; 491 util_get_context_label(context->type, hash, &label); 492 493 unsigned int count = 0; 494 struct GNUNET_GNSRECORD_Data data [3]; 495 496 if (GNUNET_YES == context->deleted) 497 goto skip_record_data; 498 499 data[count].record_type = GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_ENTRY; 500 data[count].data = &room_entry; 501 data[count].data_size = sizeof(room_entry); 502 data[count].expiration_time = expiration.abs_value_us; 503 data[count].flags = GNUNET_GNSRECORD_RF_PRIVATE; 504 count++; 505 506 if (context->nick) 507 { 508 size_t name_len = strlen(context->nick); 509 if (name_len >= sizeof(room_details.name)) 510 name_len = sizeof(room_details.name) - 1; 511 512 GNUNET_memcpy(room_details.name, context->nick, name_len); 513 room_details.name[name_len] = '\0'; 514 } 515 516 if ((context->nick) || (context->flags != 0)) 517 { 518 room_details.flags = context->flags; 519 520 data[count].record_type = GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS; 521 data[count].data = &room_details; 522 data[count].data_size = sizeof(room_details); 523 data[count].expiration_time = expiration.abs_value_us; 524 data[count].flags = ( 525 GNUNET_GNSRECORD_RF_PRIVATE | 526 GNUNET_GNSRECORD_RF_SUPPLEMENTAL 527 ); 528 529 count++; 530 } 531 532 if (topic) 533 { 534 data[count].record_type = GNUNET_DNSPARSER_TYPE_TXT; 535 data[count].data = topic; 536 data[count].data_size = strlen(topic); 537 data[count].expiration_time = expiration.abs_value_us; 538 data[count].flags = ( 539 GNUNET_GNSRECORD_RF_PRIVATE | 540 GNUNET_GNSRECORD_RF_SUPPLEMENTAL 541 ); 542 543 count++; 544 } 545 546 skip_record_data: 547 if (context->query) 548 GNUNET_NAMESTORE_cancel(context->query); 549 550 context->query = GNUNET_NAMESTORE_record_set_store( 551 context->handle->namestore, 552 zone, 553 label, 554 count, 555 data, 556 cont_context_write_records, 557 context 558 ); 559 560 GNUNET_free(label); 561 } 562 563 void 564 context_delete (struct GNUNET_CHAT_Context *context, 565 enum GNUNET_GenericReturnValue exit) 566 { 567 GNUNET_assert((context) && (context->room)); 568 569 context->deleted = GNUNET_YES; 570 context_write_records(context); 571 572 if (GNUNET_YES != exit) 573 return; 574 575 if (context->request_task) 576 { 577 GNUNET_SCHEDULER_cancel(context->request_task); 578 context->request_task = NULL; 579 } 580 581 GNUNET_MESSENGER_close_room(context->room); 582 }