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