gnunet_chat_contact.c (12054B)
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_contact.c 23 */ 24 25 #include "gnunet_chat_contact.h" 26 #include "gnunet_chat_context.h" 27 #include "gnunet_chat_handle.h" 28 #include "gnunet_chat_ticket.h" 29 30 #include "internal/gnunet_chat_tagging.h" 31 32 #include <gnunet/gnunet_common.h> 33 #include <gnunet/gnunet_messenger_service.h> 34 #include <gnunet/gnunet_time_lib.h> 35 #include <gnunet/gnunet_util_lib.h> 36 37 #include "gnunet_chat_contact_intern.c" 38 39 static const unsigned int initial_map_size_of_contact = 8; 40 41 struct GNUNET_CHAT_Contact* 42 contact_create_from_member (struct GNUNET_CHAT_Handle *handle, 43 const struct GNUNET_MESSENGER_Contact *member) 44 { 45 GNUNET_assert((handle) && (member)); 46 47 struct GNUNET_CHAT_Contact* contact = GNUNET_new(struct GNUNET_CHAT_Contact); 48 49 contact->handle = handle; 50 contact->context = NULL; 51 52 contact->destruction = NULL; 53 54 contact->member = member; 55 contact->joined = GNUNET_CONTAINER_multihashmap_create( 56 initial_map_size_of_contact, GNUNET_NO); 57 58 contact->tickets_head = NULL; 59 contact->tickets_tail = NULL; 60 61 contact->public_key = NULL; 62 contact->user_pointer = NULL; 63 64 contact->owned = GNUNET_NO; 65 66 contact_update_key (contact); 67 return contact; 68 } 69 70 void 71 contact_update_join (struct GNUNET_CHAT_Contact *contact, 72 struct GNUNET_CHAT_Context *context, 73 const struct GNUNET_HashCode *hash, 74 enum GNUNET_MESSENGER_MessageFlags flags) 75 { 76 GNUNET_assert( 77 (contact) && 78 (contact->joined) && 79 (context) && 80 (hash) 81 ); 82 83 if (!(context->room)) 84 return; 85 86 const enum GNUNET_GenericReturnValue blocked = contact_is_tagged( 87 contact, context, NULL 88 ); 89 90 const struct GNUNET_HashCode *key = GNUNET_MESSENGER_room_get_key( 91 context->room 92 ); 93 94 struct GNUNET_HashCode *current = GNUNET_CONTAINER_multihashmap_get( 95 contact->joined, 96 key 97 ); 98 99 if (! current) 100 { 101 current = GNUNET_new(struct GNUNET_HashCode); 102 103 if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put( 104 contact->joined, key, current, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)) 105 { 106 GNUNET_free(current); 107 return; 108 } 109 110 GNUNET_memcpy(current, hash, 111 sizeof(struct GNUNET_HashCode)); 112 return; 113 } 114 else if (0 == (flags & GNUNET_MESSENGER_FLAG_RECENT)) 115 return; 116 117 if (GNUNET_YES == blocked) 118 contact_untag(contact, context, NULL); 119 120 GNUNET_memcpy(current, hash, 121 sizeof(struct GNUNET_HashCode)); 122 123 if (GNUNET_YES == blocked) 124 contact_tag(contact, context, NULL); 125 } 126 127 void 128 contact_leave (struct GNUNET_CHAT_Contact *contact, 129 struct GNUNET_CHAT_Context *context) 130 { 131 GNUNET_assert( 132 (contact) && 133 (contact->joined) && 134 (context) 135 ); 136 137 if (!(context->room)) 138 return; 139 140 const struct GNUNET_HashCode *key = GNUNET_MESSENGER_room_get_key( 141 context->room 142 ); 143 144 struct GNUNET_HashCode *current = GNUNET_CONTAINER_multihashmap_get( 145 contact->joined, 146 key 147 ); 148 149 if ((! current) || 150 (GNUNET_YES != GNUNET_CONTAINER_multihashmap_remove(contact->joined, key, current))) 151 return; 152 153 GNUNET_free(current); 154 } 155 156 void 157 contact_update_key (struct GNUNET_CHAT_Contact *contact) 158 { 159 GNUNET_assert(contact); 160 161 if (contact->public_key) 162 GNUNET_free(contact->public_key); 163 164 const struct GNUNET_CRYPTO_BlindablePublicKey *pubkey; 165 pubkey = contact_get_key(contact); 166 167 if (pubkey) 168 contact->public_key = GNUNET_CRYPTO_blindable_public_key_to_string(pubkey); 169 else 170 contact->public_key = NULL; 171 } 172 173 const struct GNUNET_CRYPTO_BlindablePublicKey* 174 contact_get_key (const struct GNUNET_CHAT_Contact *contact) 175 { 176 GNUNET_assert(contact); 177 178 if (!(contact->member)) 179 return NULL; 180 181 return GNUNET_MESSENGER_contact_get_key(contact->member); 182 } 183 184 struct GNUNET_CHAT_Context* 185 contact_find_context (const struct GNUNET_CHAT_Contact *contact, 186 enum GNUNET_GenericReturnValue room_required) 187 { 188 GNUNET_assert(contact); 189 190 if ((contact->context) && 191 ((GNUNET_YES != room_required) || (contact->context->room))) 192 return contact->context; 193 194 struct GNUNET_CHAT_ContactFindRoom find; 195 find.member_count = 0; 196 find.room = NULL; 197 198 GNUNET_MESSENGER_find_rooms( 199 contact->handle->messenger, 200 contact->member, 201 it_contact_find_room, 202 &find 203 ); 204 205 if (!(find.room)) 206 return NULL; 207 208 struct GNUNET_CHAT_Context *context = GNUNET_CONTAINER_multihashmap_get( 209 contact->handle->contexts, 210 GNUNET_MESSENGER_room_get_key(find.room) 211 ); 212 213 if ((GNUNET_YES == room_required) && (!(context->room))) 214 return NULL; 215 216 return context; 217 } 218 219 const struct GNUNET_HashCode* 220 get_contact_join_hash (const struct GNUNET_CHAT_Contact *contact, 221 const struct GNUNET_CHAT_Context *context) 222 { 223 GNUNET_assert((contact) && (context)); 224 225 if (!(context->room)) 226 return NULL; 227 228 return GNUNET_CONTAINER_multihashmap_get( 229 contact->joined, 230 GNUNET_MESSENGER_room_get_key(context->room) 231 ); 232 } 233 234 enum GNUNET_GenericReturnValue 235 contact_is_tagged (const struct GNUNET_CHAT_Contact *contact, 236 const struct GNUNET_CHAT_Context *context, 237 const char *tag) 238 { 239 GNUNET_assert( 240 (contact) && 241 (contact->joined) 242 ); 243 244 const enum GNUNET_GenericReturnValue general = ( 245 context ? GNUNET_NO : GNUNET_YES 246 ); 247 248 if (context) 249 goto skip_context_search; 250 251 struct GNUNET_CONTAINER_MultiHashMapIterator *iter; 252 iter = GNUNET_CONTAINER_multihashmap_iterator_create( 253 contact->joined 254 ); 255 256 if (iter) 257 { 258 struct GNUNET_HashCode key; 259 const void *value; 260 261 while (! context) 262 { 263 if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_iterator_next( 264 iter, &key, &value)) 265 break; 266 267 context = GNUNET_CONTAINER_multihashmap_get( 268 contact->handle->contexts, &key); 269 } 270 271 GNUNET_CONTAINER_multihashmap_iterator_destroy(iter); 272 } 273 274 skip_context_search: 275 if (! context) 276 return GNUNET_NO; 277 278 const struct GNUNET_HashCode *hash = get_contact_join_hash( 279 contact, context); 280 281 if (! hash) 282 return (general == GNUNET_YES? 283 GNUNET_NO : 284 contact_is_tagged(contact, NULL, tag) 285 ); 286 287 const struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get( 288 context->taggings, 289 hash 290 ); 291 292 if (! tagging) 293 return GNUNET_NO; 294 295 struct GNUNET_CHAT_ContactFindTag find; 296 find.hash = NULL; 297 298 internal_tagging_iterate( 299 tagging, 300 GNUNET_NO, 301 tag, 302 it_contact_find_tag, 303 &find 304 ); 305 306 if (find.hash) 307 return GNUNET_YES; 308 else 309 return GNUNET_NO; 310 } 311 312 void 313 contact_untag (struct GNUNET_CHAT_Contact *contact, 314 struct GNUNET_CHAT_Context *context, 315 const char *tag) 316 { 317 GNUNET_assert( 318 (contact) && 319 (contact->joined) && 320 (context) 321 ); 322 323 const struct GNUNET_HashCode *hash = get_contact_join_hash( 324 contact, context); 325 326 if (! hash) 327 return; 328 329 const struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get( 330 context->taggings, 331 hash 332 ); 333 334 if (! tagging) 335 return; 336 337 struct GNUNET_CHAT_ContactFindTag find; 338 find.hash = NULL; 339 340 internal_tagging_iterate( 341 tagging, 342 GNUNET_NO, 343 tag, 344 it_contact_find_tag, 345 &find 346 ); 347 348 if ((! find.hash) || (! context->room)) 349 return; 350 351 GNUNET_MESSENGER_delete_message( 352 context->room, 353 find.hash, 354 GNUNET_TIME_relative_get_zero_() 355 ); 356 } 357 358 void 359 contact_tag (struct GNUNET_CHAT_Contact *contact, 360 struct GNUNET_CHAT_Context *context, 361 const char *tag) 362 { 363 GNUNET_assert( 364 (contact) && 365 (contact->joined) && 366 (context) 367 ); 368 369 const struct GNUNET_HashCode *hash = get_contact_join_hash( 370 contact, context); 371 372 if (! hash) 373 return; 374 375 const struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get( 376 context->taggings, 377 hash 378 ); 379 380 if (! tagging) 381 goto skip_tag_search; 382 383 struct GNUNET_CHAT_ContactFindTag find; 384 find.hash = NULL; 385 386 internal_tagging_iterate( 387 tagging, 388 GNUNET_NO, 389 tag, 390 it_contact_find_tag, 391 &find 392 ); 393 394 if (find.hash) 395 return; 396 397 skip_tag_search: 398 if (! context->room) 399 return; 400 401 char *tag_value = tag? GNUNET_strdup(tag) : NULL; 402 403 struct GNUNET_MESSENGER_Message msg; 404 memset(&msg, 0, sizeof(msg)); 405 406 msg.header.kind = GNUNET_MESSENGER_KIND_TAG; 407 GNUNET_memcpy(&(msg.body.tag.hash), hash, 408 sizeof(struct GNUNET_HashCode)); 409 msg.body.tag.tag = tag_value; 410 411 GNUNET_MESSENGER_send_message( 412 context->room, 413 &msg, 414 contact->member 415 ); 416 417 if (tag_value) 418 GNUNET_free(tag_value); 419 } 420 421 int 422 contact_iterate_tags (struct GNUNET_CHAT_Contact *contact, 423 struct GNUNET_CHAT_Context *context, 424 GNUNET_CHAT_ContactTagCallback callback, 425 void *cls) 426 { 427 GNUNET_assert((contact) && (contact->joined)); 428 429 if (! context) 430 { 431 struct GNUNET_CHAT_ContactIterateUniqueTag it; 432 it.tags = GNUNET_CONTAINER_multihashmap_create( 433 initial_map_size_of_contact, GNUNET_NO); 434 it.callback = callback; 435 it.cls = cls; 436 437 if (! (it.tags)) 438 return GNUNET_SYSERR; 439 440 int result = GNUNET_SYSERR; 441 442 struct GNUNET_CONTAINER_MultiHashMapIterator *iter; 443 iter = GNUNET_CONTAINER_multihashmap_iterator_create( 444 contact->joined 445 ); 446 447 if (! iter) 448 goto free_tags_iteration; 449 450 struct GNUNET_HashCode key; 451 const void *value; 452 453 while (! context) 454 { 455 if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_iterator_next( 456 iter, &key, &value)) 457 break; 458 459 context = GNUNET_CONTAINER_multihashmap_get( 460 contact->handle->contexts, &key); 461 462 if (context) 463 result = contact_iterate_tags( 464 contact, 465 context, 466 it_contact_iterate_unique_tag, 467 &it 468 ); 469 } 470 471 GNUNET_CONTAINER_multihashmap_iterator_destroy(iter); 472 473 free_tags_iteration: 474 GNUNET_CONTAINER_multihashmap_destroy(it.tags); 475 return result; 476 } 477 478 const struct GNUNET_HashCode *hash = get_contact_join_hash( 479 contact, context); 480 481 if (! hash) 482 return GNUNET_SYSERR; 483 484 const struct GNUNET_CHAT_InternalTagging *tagging = GNUNET_CONTAINER_multihashmap_get( 485 context->taggings, 486 hash 487 ); 488 489 if (! tagging) 490 return 0; 491 492 struct GNUNET_CHAT_ContactIterateTag it; 493 it.contact = contact; 494 it.callback = callback; 495 it.cls = cls; 496 497 return internal_tagging_iterate( 498 tagging, 499 GNUNET_YES, 500 NULL, 501 it_contact_iterate_tag, 502 &it 503 ); 504 } 505 506 void 507 contact_destroy (struct GNUNET_CHAT_Contact* contact) 508 { 509 GNUNET_assert(contact); 510 511 if (contact->destruction) 512 GNUNET_SCHEDULER_cancel(contact->destruction); 513 514 struct GNUNET_CHAT_InternalTickets *tickets; 515 while (contact->tickets_head) 516 { 517 tickets = contact->tickets_head; 518 519 GNUNET_CONTAINER_DLL_remove( 520 contact->tickets_head, 521 contact->tickets_tail, 522 tickets 523 ); 524 525 ticket_destroy(tickets->ticket); 526 527 GNUNET_free(tickets); 528 } 529 530 if (contact->public_key) 531 GNUNET_free(contact->public_key); 532 533 if (contact->joined) 534 { 535 GNUNET_CONTAINER_multihashmap_iterate( 536 contact->joined, it_free_join_hashes, NULL 537 ); 538 539 GNUNET_CONTAINER_multihashmap_destroy(contact->joined); 540 } 541 542 if ((contact->context) && (!(contact->context->room))) 543 context_destroy(contact->context); 544 545 GNUNET_free(contact); 546 }