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