client.c (35051B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21 22#include "encode-jpeg.h" 23#include "encode-png.h" 24#include "encode-webp.h" 25#include "guacamole/mem.h" 26#include "guacamole/client.h" 27#include "guacamole/error.h" 28#include "guacamole/layer.h" 29#include "guacamole/plugin.h" 30#include "guacamole/pool.h" 31#include "guacamole/protocol.h" 32#include "guacamole/rwlock.h" 33#include "guacamole/socket.h" 34#include "guacamole/stream.h" 35#include "guacamole/string.h" 36#include "guacamole/timestamp.h" 37#include "guacamole/user.h" 38#include "id.h" 39 40#include <dlfcn.h> 41#include <errno.h> 42#include <inttypes.h> 43#include <pthread.h> 44#include <signal.h> 45#include <stdarg.h> 46#include <stdio.h> 47#include <stdlib.h> 48#include <string.h> 49 50/** 51 * The number of nanoseconds between times that the pending users list will be 52 * synchronized and emptied (250 milliseconds aka 1/4 second). 53 */ 54#define GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL 250000000 55 56/** 57 * A value that indicates that the pending users timer has yet to be 58 * initialized and started. 59 */ 60#define GUAC_CLIENT_PENDING_TIMER_UNREGISTERED 0 61 62/** 63 * A value that indicates that the pending users timer has been initialized 64 * and started, but that the timer handler is not currently running. 65 */ 66#define GUAC_CLIENT_PENDING_TIMER_REGISTERED 1 67 68/** 69 * A value that indicates that the pending users timer has been initialized 70 * and started, and that the timer handler is currently running. 71 */ 72#define GUAC_CLIENT_PENDING_TIMER_TRIGGERED 2 73 74/** 75 * Empty NULL-terminated array of argument names. 76 */ 77const char* __GUAC_CLIENT_NO_ARGS[] = { NULL }; 78 79guac_layer __GUAC_DEFAULT_LAYER = { 80 .index = 0 81}; 82 83const guac_layer* GUAC_DEFAULT_LAYER = &__GUAC_DEFAULT_LAYER; 84 85guac_layer* guac_client_alloc_layer(guac_client* client) { 86 87 /* Init new layer */ 88 guac_layer* allocd_layer = guac_mem_alloc(sizeof(guac_layer)); 89 allocd_layer->index = guac_pool_next_int(client->__layer_pool)+1; 90 91 return allocd_layer; 92 93} 94 95guac_layer* guac_client_alloc_buffer(guac_client* client) { 96 97 /* Init new layer */ 98 guac_layer* allocd_layer = guac_mem_alloc(sizeof(guac_layer)); 99 allocd_layer->index = -guac_pool_next_int(client->__buffer_pool) - 1; 100 101 return allocd_layer; 102 103} 104 105void guac_client_free_buffer(guac_client* client, guac_layer* layer) { 106 107 /* Release index to pool */ 108 guac_pool_free_int(client->__buffer_pool, -layer->index - 1); 109 110 /* Free layer */ 111 guac_mem_free(layer); 112 113} 114 115void guac_client_free_layer(guac_client* client, guac_layer* layer) { 116 117 /* Release index to pool */ 118 guac_pool_free_int(client->__layer_pool, layer->index); 119 120 /* Free layer */ 121 guac_mem_free(layer); 122 123} 124 125guac_stream* guac_client_alloc_stream(guac_client* client) { 126 127 guac_stream* allocd_stream; 128 int stream_index; 129 130 /* Refuse to allocate beyond maximum */ 131 if (client->__stream_pool->active == GUAC_CLIENT_MAX_STREAMS) 132 return NULL; 133 134 /* Allocate stream */ 135 stream_index = guac_pool_next_int(client->__stream_pool); 136 137 /* Initialize stream with odd index (even indices are user-level) */ 138 allocd_stream = &(client->__output_streams[stream_index]); 139 allocd_stream->index = (stream_index * 2) + 1; 140 allocd_stream->data = NULL; 141 allocd_stream->ack_handler = NULL; 142 allocd_stream->blob_handler = NULL; 143 allocd_stream->end_handler = NULL; 144 145 return allocd_stream; 146 147} 148 149void guac_client_free_stream(guac_client* client, guac_stream* stream) { 150 151 /* Release index to pool */ 152 guac_pool_free_int(client->__stream_pool, (stream->index - 1) / 2); 153 154 /* Mark stream as closed */ 155 stream->index = GUAC_CLIENT_CLOSED_STREAM_INDEX; 156 157} 158 159/** 160 * Promote all pending users to full users, calling the join pending handler 161 * before, if any. 162 * 163 * @param data 164 * The client for which all pending users should be promoted. 165 */ 166static void guac_client_promote_pending_users(union sigval data) { 167 168 guac_client* client = (guac_client*) data.sival_ptr; 169 170 pthread_mutex_lock(&(client->__pending_users_timer_mutex)); 171 172 /* Check if the previous instance of this handler is still running */ 173 int already_running = ( 174 client->__pending_users_timer_state 175 == GUAC_CLIENT_PENDING_TIMER_TRIGGERED); 176 177 /* Mark the handler as running if it isn't already */ 178 client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_TRIGGERED; 179 180 pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); 181 182 /* Do not start the handler if the previous instance is still running */ 183 if (already_running) 184 return; 185 186 /* Acquire the lock for reading and modifying the list of pending users */ 187 guac_rwlock_acquire_write_lock(&(client->__pending_users_lock)); 188 189 /* Skip user promotion entirely if there's no pending users */ 190 if (client->__pending_users == NULL) 191 goto promotion_complete; 192 193 /* Run the pending join handler, if one is defined */ 194 if (client->join_pending_handler) { 195 196 /* If an error occurs in the pending handler */ 197 if(client->join_pending_handler(client)) { 198 199 /* Log a warning and abort the promotion of the pending users */ 200 guac_client_log(client, GUAC_LOG_WARNING, 201 "join_pending_handler did not successfully complete;" 202 " any pending users have not been promoted.\n"); 203 204 goto promotion_complete; 205 } 206 } 207 208 /* The first pending user in the list, if any */ 209 guac_user* first_user = client->__pending_users; 210 211 /* The final user in the list, if any */ 212 guac_user* last_user = first_user; 213 214 /* Iterate through the pending users to find the final user */ 215 guac_user* user = first_user; 216 while (user != NULL) { 217 last_user = user; 218 user = user->__next; 219 } 220 221 /* Mark the list as empty */ 222 client->__pending_users = NULL; 223 224 /* Acquire the lock for reading and modifying the list of full users. */ 225 guac_rwlock_acquire_write_lock(&(client->__users_lock)); 226 227 /* If any users were removed from the pending list, promote them now */ 228 if (last_user != NULL) { 229 230 /* Add all formerly-pending users to the start of the user list */ 231 if (client->__users != NULL) 232 client->__users->__prev = last_user; 233 234 last_user->__next = client->__users; 235 client->__users = first_user; 236 237 } 238 239 guac_rwlock_release_lock(&(client->__users_lock)); 240 241promotion_complete: 242 243 /* Release the lock (this is done AFTER updating the connected user list 244 * to ensure that all users are always on exactly one of these lists) */ 245 guac_rwlock_release_lock(&(client->__pending_users_lock)); 246 247 /* Mark the handler as complete so the next instance can run */ 248 pthread_mutex_lock(&(client->__pending_users_timer_mutex)); 249 client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_REGISTERED; 250 pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); 251 252} 253 254guac_client* guac_client_alloc() { 255 256 int i; 257 258 /* Allocate new client */ 259 guac_client* client = guac_mem_alloc(sizeof(guac_client)); 260 if (client == NULL) { 261 guac_error = GUAC_STATUS_NO_MEMORY; 262 guac_error_message = "Could not allocate memory for client"; 263 return NULL; 264 } 265 266 /* Init new client */ 267 memset(client, 0, sizeof(guac_client)); 268 269 client->args = __GUAC_CLIENT_NO_ARGS; 270 client->state = GUAC_CLIENT_RUNNING; 271 client->last_sent_timestamp = guac_timestamp_current(); 272 273 /* Generate ID */ 274 client->connection_id = guac_generate_id(GUAC_CLIENT_ID_PREFIX); 275 if (client->connection_id == NULL) { 276 guac_mem_free(client); 277 return NULL; 278 } 279 280 /* Allocate buffer and layer pools */ 281 client->__buffer_pool = guac_pool_alloc(GUAC_BUFFER_POOL_INITIAL_SIZE); 282 client->__layer_pool = guac_pool_alloc(GUAC_BUFFER_POOL_INITIAL_SIZE); 283 284 /* Allocate stream pool */ 285 client->__stream_pool = guac_pool_alloc(0); 286 287 /* Initialize streams */ 288 client->__output_streams = guac_mem_alloc(sizeof(guac_stream), GUAC_CLIENT_MAX_STREAMS); 289 290 for (i=0; i<GUAC_CLIENT_MAX_STREAMS; i++) { 291 client->__output_streams[i].index = GUAC_CLIENT_CLOSED_STREAM_INDEX; 292 } 293 294 /* Init locks */ 295 guac_rwlock_init(&(client->__users_lock)); 296 guac_rwlock_init(&(client->__pending_users_lock)); 297 298 /* Initialize the write lock flags to 0, as threads won't have yet */ 299 pthread_key_create(&(client->__users_lock.key), (void *) 0); 300 pthread_key_create(&(client->__pending_users_lock.key), (void *) 0); 301 302 /* The timer will be lazily created in the child process */ 303 client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_UNREGISTERED; 304 305 /* Set up the pending user promotion mutex */ 306 pthread_mutex_init(&(client->__pending_users_timer_mutex), NULL); 307 308 /* Set up broadcast sockets */ 309 client->socket = guac_socket_broadcast(client); 310 client->pending_socket = guac_socket_broadcast_pending(client); 311 312 return client; 313 314} 315 316void guac_client_free(guac_client* client) { 317 318 /* Acquire write locks before referencing user pointers */ 319 guac_rwlock_acquire_write_lock(&(client->__pending_users_lock)); 320 guac_rwlock_acquire_write_lock(&(client->__users_lock)); 321 322 /* Remove all pending users */ 323 while (client->__pending_users != NULL) 324 guac_client_remove_user(client, client->__pending_users); 325 326 /* Remove all users */ 327 while (client->__users != NULL) 328 guac_client_remove_user(client, client->__users); 329 330 /* Release the locks */ 331 guac_rwlock_release_lock(&(client->__users_lock)); 332 guac_rwlock_release_lock(&(client->__pending_users_lock)); 333 334 if (client->free_handler) { 335 336 /* FIXME: Errors currently ignored... */ 337 client->free_handler(client); 338 339 } 340 341 /* Free sockets */ 342 guac_socket_free(client->socket); 343 guac_socket_free(client->pending_socket); 344 345 /* Free layer pools */ 346 guac_pool_free(client->__buffer_pool); 347 guac_pool_free(client->__layer_pool); 348 349 /* Free streams */ 350 guac_mem_free(client->__output_streams); 351 352 /* Free stream pool */ 353 guac_pool_free(client->__stream_pool); 354 355 /* Close associated plugin */ 356 if (client->__plugin_handle != NULL) { 357 if (dlclose(client->__plugin_handle)) 358 guac_client_log(client, GUAC_LOG_ERROR, "Unable to close plugin: %s", dlerror()); 359 } 360 361 /* Find out if the pending user promotion timer was ever started */ 362 pthread_mutex_lock(&(client->__pending_users_timer_mutex)); 363 int was_started = ( 364 client->__pending_users_timer_state 365 != GUAC_CLIENT_PENDING_TIMER_UNREGISTERED); 366 pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); 367 368 /* If the timer was registered, stop it before destroying the lock */ 369 if (was_started) 370 timer_delete(client->__pending_users_timer); 371 372 pthread_mutex_destroy(&(client->__pending_users_timer_mutex)); 373 374 /* Destroy the reentrant read-write locks */ 375 guac_rwlock_destroy(&(client->__users_lock)); 376 guac_rwlock_destroy(&(client->__pending_users_lock)); 377 378 guac_mem_free(client->connection_id); 379 guac_mem_free(client); 380} 381 382void vguac_client_log(guac_client* client, guac_client_log_level level, 383 const char* format, va_list ap) { 384 385 /* Call handler if defined */ 386 if (client->log_handler != NULL) 387 client->log_handler(client, level, format, ap); 388 389} 390 391void guac_client_log(guac_client* client, guac_client_log_level level, 392 const char* format, ...) { 393 394 va_list args; 395 va_start(args, format); 396 397 vguac_client_log(client, level, format, args); 398 399 va_end(args); 400 401} 402 403void guac_client_stop(guac_client* client) { 404 client->state = GUAC_CLIENT_STOPPING; 405} 406 407void vguac_client_abort(guac_client* client, guac_protocol_status status, 408 const char* format, va_list ap) { 409 410 /* Only relevant if client is running */ 411 if (client->state == GUAC_CLIENT_RUNNING) { 412 413 /* Log detail of error */ 414 vguac_client_log(client, GUAC_LOG_ERROR, format, ap); 415 416 /* Send error immediately, limit information given */ 417 guac_protocol_send_error(client->socket, "Aborted. See logs.", status); 418 guac_socket_flush(client->socket); 419 420 /* Stop client */ 421 guac_client_stop(client); 422 423 } 424 425} 426 427void guac_client_abort(guac_client* client, guac_protocol_status status, 428 const char* format, ...) { 429 430 va_list args; 431 va_start(args, format); 432 433 vguac_client_abort(client, status, format, args); 434 435 va_end(args); 436 437} 438 439/** 440 * Add the provided user to the list of pending users who have yet to have 441 * their connection state synchronized after joining, for the connection 442 * associated with the given guac client. 443 * 444 * @param client 445 * The client associated with the connection for which the provided user 446 * is pending a connection state synchronization after joining. 447 * 448 * @param user 449 * The user to add to the pending list. 450 */ 451static void guac_client_add_pending_user( 452 guac_client* client, guac_user* user) { 453 454 /* Acquire the lock for modifying the list of pending users */ 455 guac_rwlock_acquire_write_lock(&(client->__pending_users_lock)); 456 457 user->__prev = NULL; 458 user->__next = client->__pending_users; 459 460 if (client->__pending_users != NULL) 461 client->__pending_users->__prev = user; 462 463 client->__pending_users = user; 464 465 /* Increment the user count */ 466 client->connected_users++; 467 468 /* Release the lock */ 469 guac_rwlock_release_lock(&(client->__pending_users_lock)); 470 471} 472 473/** 474 * Periodically promote pending users to full users. Returns zero if the timer 475 * is already running, or successfully created, or a non-zero value if the 476 * timer could not be created and started. 477 * 478 * @param client 479 * The guac client for which the new timer should be started, if not 480 * already running. 481 * 482 * @return 483 * Zero if the timer was successfully created and started, or a negative 484 * value otherwise. 485 */ 486static int guac_client_start_pending_users_timer(guac_client* client) { 487 488 pthread_mutex_lock(&(client->__pending_users_timer_mutex)); 489 490 /* Return success if the timer is already created and running */ 491 if (client->__pending_users_timer_state 492 != GUAC_CLIENT_PENDING_TIMER_UNREGISTERED) { 493 pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); 494 return 0; 495 } 496 497 /* Configure the timer to synchronize and clear the pending users */ 498 struct sigevent signal_config = { 499 .sigev_notify = SIGEV_THREAD, 500 .sigev_notify_function = guac_client_promote_pending_users, 501 .sigev_value = { .sival_ptr = client }}; 502 503 /* Create a timer to synchronize any pending users periodically */ 504 if (timer_create( 505 CLOCK_MONOTONIC, 506 &signal_config, 507 &(client->__pending_users_timer))) { 508 pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); 509 return 1; 510 } 511 512 /* Configure the pending users timer to run on the defined interval */ 513 struct itimerspec time_config = { 514 .it_interval = { .tv_nsec = GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL }, 515 .it_value = { .tv_nsec = GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL } 516 }; 517 518 /* Start the timer */ 519 if (timer_settime( 520 client->__pending_users_timer, 0, &time_config, NULL) < 0) { 521 timer_delete(client->__pending_users_timer); 522 pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); 523 return 1; 524 } 525 526 /* Mark the timer as registered but not yet running */ 527 client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_REGISTERED; 528 529 pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); 530 return 0; 531 532} 533 534int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** argv) { 535 536 /* Create and start the timer if it hasn't already been initialized */ 537 if (guac_client_start_pending_users_timer(client)) { 538 539 /** 540 * 541 * If the timer could not be created, do not add the user - they cannot 542 * be synchronized without the timer. 543 */ 544 guac_client_log(client, GUAC_LOG_ERROR, 545 "Could not start pending user timer: %s.", strerror(errno)); 546 return 1; 547 } 548 549 int retval = 0; 550 551 /* Call handler, if defined */ 552 if (client->join_handler) 553 retval = client->join_handler(user, argc, argv); 554 555 if (retval == 0) { 556 557 /* 558 * Add the user to the list of pending users, to have their connection 559 * state synchronized asynchronously. 560 */ 561 guac_client_add_pending_user(client, user); 562 563 /* Update owner pointer if user is owner */ 564 if (user->owner) 565 client->__owner = user; 566 567 } 568 569 /* Notify owner of user joining connection. */ 570 if (retval == 0 && !user->owner) 571 guac_client_owner_notify_join(client, user); 572 573 return retval; 574 575} 576 577void guac_client_remove_user(guac_client* client, guac_user* user) { 578 579 guac_rwlock_acquire_write_lock(&(client->__pending_users_lock)); 580 guac_rwlock_acquire_write_lock(&(client->__users_lock)); 581 582 /* Update prev / head */ 583 if (user->__prev != NULL) 584 user->__prev->__next = user->__next; 585 else if (client->__users == user) 586 client->__users = user->__next; 587 else if (client->__pending_users == user) 588 client->__pending_users = user->__next; 589 590 /* Update next */ 591 if (user->__next != NULL) 592 user->__next->__prev = user->__prev; 593 594 client->connected_users--; 595 596 /* Update owner pointer if user was owner */ 597 if (user->owner) 598 client->__owner = NULL; 599 600 guac_rwlock_release_lock(&(client->__users_lock)); 601 guac_rwlock_release_lock(&(client->__pending_users_lock)); 602 603 /* Update owner of user having left the connection. */ 604 if (!user->owner) 605 guac_client_owner_notify_leave(client, user); 606 607 /* Call handler, if defined */ 608 if (user->leave_handler) 609 user->leave_handler(user); 610 else if (client->leave_handler) 611 client->leave_handler(user); 612 613} 614 615void guac_client_foreach_user(guac_client* client, guac_user_callback* callback, void* data) { 616 617 guac_user* current; 618 619 guac_rwlock_acquire_read_lock(&(client->__users_lock)); 620 621 /* Call function on each user */ 622 current = client->__users; 623 while (current != NULL) { 624 callback(current, data); 625 current = current->__next; 626 } 627 628 guac_rwlock_release_lock(&(client->__users_lock)); 629 630} 631 632void guac_client_foreach_pending_user( 633 guac_client* client, guac_user_callback* callback, void* data) { 634 635 guac_user* current; 636 637 guac_rwlock_acquire_read_lock(&(client->__pending_users_lock)); 638 639 /* Call function on each pending user */ 640 current = client->__pending_users; 641 while (current != NULL) { 642 callback(current, data); 643 current = current->__next; 644 } 645 646 guac_rwlock_release_lock(&(client->__pending_users_lock)); 647 648} 649 650void* guac_client_for_owner(guac_client* client, guac_user_callback* callback, 651 void* data) { 652 653 void* retval; 654 655 guac_rwlock_acquire_read_lock(&(client->__users_lock)); 656 657 /* Invoke callback with current owner */ 658 retval = callback(client->__owner, data); 659 660 guac_rwlock_release_lock(&(client->__users_lock)); 661 662 /* Return value from callback */ 663 return retval; 664 665} 666 667void* guac_client_for_user(guac_client* client, guac_user* user, 668 guac_user_callback* callback, void* data) { 669 670 guac_user* current; 671 672 int user_valid = 0; 673 void* retval; 674 675 guac_rwlock_acquire_read_lock(&(client->__users_lock)); 676 677 /* Loop through all users, searching for a pointer to the given user */ 678 current = client->__users; 679 while (current != NULL) { 680 681 /* If the user's pointer exists in the list, they are indeed valid */ 682 if (current == user) { 683 user_valid = 1; 684 break; 685 } 686 687 current = current->__next; 688 } 689 690 /* Use NULL if user does not actually exist */ 691 if (!user_valid) 692 user = NULL; 693 694 /* Invoke callback with requested user (if they exist) */ 695 retval = callback(user, data); 696 697 guac_rwlock_release_lock(&(client->__users_lock)); 698 699 /* Return value from callback */ 700 return retval; 701 702} 703 704int guac_client_end_frame(guac_client* client) { 705 706 /* Update and send timestamp */ 707 client->last_sent_timestamp = guac_timestamp_current(); 708 709 /* Log received timestamp and calculated lag (at TRACE level only) */ 710 guac_client_log(client, GUAC_LOG_TRACE, "Server completed " 711 "frame %" PRIu64 "ms.", client->last_sent_timestamp); 712 713 return guac_protocol_send_sync(client->socket, client->last_sent_timestamp); 714 715} 716 717int guac_client_load_plugin(guac_client* client, const char* protocol) { 718 719 /* Reference to dlopen()'d plugin */ 720 void* client_plugin_handle; 721 722 /* Pluggable client */ 723 char protocol_lib[GUAC_PROTOCOL_LIBRARY_LIMIT] = 724 GUAC_PROTOCOL_LIBRARY_PREFIX; 725 726 /* Type-pun for the sake of dlsym() - cannot typecast a void* to a function 727 * pointer otherwise */ 728 union { 729 guac_client_init_handler* client_init; 730 void* obj; 731 } alias; 732 733 /* Add protocol and .so suffix to protocol_lib */ 734 guac_strlcat(protocol_lib, protocol, sizeof(protocol_lib)); 735 if (guac_strlcat(protocol_lib, GUAC_PROTOCOL_LIBRARY_SUFFIX, 736 sizeof(protocol_lib)) >= sizeof(protocol_lib)) { 737 guac_error = GUAC_STATUS_NO_MEMORY; 738 guac_error_message = "Protocol name is too long"; 739 return -1; 740 } 741 742 /* Load client plugin */ 743 client_plugin_handle = dlopen(protocol_lib, RTLD_LAZY); 744 if (!client_plugin_handle) { 745 guac_error = GUAC_STATUS_NOT_FOUND; 746 guac_error_message = dlerror(); 747 return -1; 748 } 749 750 dlerror(); /* Clear errors */ 751 752 /* Get init function */ 753 alias.obj = dlsym(client_plugin_handle, "guac_client_init"); 754 755 /* Fail if cannot find guac_client_init */ 756 if (dlerror() != NULL) { 757 guac_error = GUAC_STATUS_INTERNAL_ERROR; 758 guac_error_message = dlerror(); 759 dlclose(client_plugin_handle); 760 return -1; 761 } 762 763 /* Init client */ 764 client->__plugin_handle = client_plugin_handle; 765 766 return alias.client_init(client); 767 768} 769 770/** 771 * A callback function which is invoked by guac_client_owner_send_required() to 772 * send the required parameters to the specified user, who is the owner of the 773 * client session. 774 * 775 * @param user 776 * The guac_user that will receive the required parameters, who is the owner 777 * of the client. 778 * 779 * @param data 780 * A pointer to a NULL-terminated array of required parameters that will be 781 * passed on to the owner to continue the connection. 782 * 783 * @return 784 * Zero if the operation succeeds or non-zero on failure, cast as a void*. 785 */ 786static void* guac_client_owner_send_required_callback(guac_user* user, void* data) { 787 788 const char** required = (const char **) data; 789 790 /* Send required parameters to owner. */ 791 if (user != NULL) 792 return (void*) ((intptr_t) guac_protocol_send_required(user->socket, required)); 793 794 return (void*) ((intptr_t) -1); 795 796} 797 798int guac_client_owner_send_required(guac_client* client, const char** required) { 799 800 /* Don't send required instruction if client does not support it. */ 801 if (!guac_client_owner_supports_required(client)) 802 return -1; 803 804 return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_send_required_callback, required)); 805 806} 807 808/** 809 * Updates the provided approximate processing lag, taking into account the 810 * processing lag of the given user. 811 * 812 * @param user 813 * The guac_user to use to update the approximate processing lag. 814 * 815 * @param data 816 * Pointer to an int containing the current approximate processing lag. 817 * The int will be updated according to the processing lag of the given 818 * user. 819 * 820 * @return 821 * Always NULL. 822 */ 823static void* __calculate_lag(guac_user* user, void* data) { 824 825 int* processing_lag = (int*) data; 826 827 /* Simply find maximum */ 828 if (user->processing_lag > *processing_lag) 829 *processing_lag = user->processing_lag; 830 831 return NULL; 832 833} 834 835int guac_client_get_processing_lag(guac_client* client) { 836 837 int processing_lag = 0; 838 839 /* Approximate the processing lag of all users */ 840 guac_client_foreach_user(client, __calculate_lag, &processing_lag); 841 842 return processing_lag; 843 844} 845 846void guac_client_stream_argv(guac_client* client, guac_socket* socket, 847 const char* mimetype, const char* name, const char* value) { 848 849 /* Allocate new stream for argument value */ 850 guac_stream* stream = guac_client_alloc_stream(client); 851 852 /* Declare stream as containing connection parameter data */ 853 guac_protocol_send_argv(socket, stream, mimetype, name); 854 855 /* Write parameter data */ 856 guac_protocol_send_blobs(socket, stream, value, strlen(value)); 857 858 /* Terminate stream */ 859 guac_protocol_send_end(socket, stream); 860 861 /* Free allocated stream */ 862 guac_client_free_stream(client, stream); 863 864} 865 866void guac_client_stream_png(guac_client* client, guac_socket* socket, 867 guac_composite_mode mode, const guac_layer* layer, int x, int y, 868 cairo_surface_t* surface) { 869 870 /* Allocate new stream for image */ 871 guac_stream* stream = guac_client_alloc_stream(client); 872 873 /* Declare stream as containing image data */ 874 guac_protocol_send_img(socket, stream, mode, layer, "image/png", x, y); 875 876 /* Write PNG data */ 877 guac_png_write(socket, stream, surface); 878 879 /* Terminate stream */ 880 guac_protocol_send_end(socket, stream); 881 882 /* Free allocated stream */ 883 guac_client_free_stream(client, stream); 884 885} 886 887void guac_client_stream_jpeg(guac_client* client, guac_socket* socket, 888 guac_composite_mode mode, const guac_layer* layer, int x, int y, 889 cairo_surface_t* surface, int quality) { 890 891 /* Allocate new stream for image */ 892 guac_stream* stream = guac_client_alloc_stream(client); 893 894 /* Declare stream as containing image data */ 895 guac_protocol_send_img(socket, stream, mode, layer, "image/jpeg", x, y); 896 897 /* Write JPEG data */ 898 guac_jpeg_write(socket, stream, surface, quality); 899 900 /* Terminate stream */ 901 guac_protocol_send_end(socket, stream); 902 903 /* Free allocated stream */ 904 guac_client_free_stream(client, stream); 905 906} 907 908void guac_client_stream_webp(guac_client* client, guac_socket* socket, 909 guac_composite_mode mode, const guac_layer* layer, int x, int y, 910 cairo_surface_t* surface, int quality, int lossless) { 911 912#ifdef ENABLE_WEBP 913 /* Allocate new stream for image */ 914 guac_stream* stream = guac_client_alloc_stream(client); 915 916 /* Declare stream as containing image data */ 917 guac_protocol_send_img(socket, stream, mode, layer, "image/webp", x, y); 918 919 /* Write WebP data */ 920 guac_webp_write(socket, stream, surface, quality, lossless); 921 922 /* Terminate stream */ 923 guac_protocol_send_end(socket, stream); 924 925 /* Free allocated stream */ 926 guac_client_free_stream(client, stream); 927#else 928 /* Do nothing if WebP support is not built in */ 929#endif 930 931} 932 933#ifdef ENABLE_WEBP 934/** 935 * Callback which is invoked by guac_client_supports_webp() for each user 936 * associated with the given client, thus updating an overall support flag 937 * describing the WebP support state for the client as a whole. 938 * 939 * @param user 940 * The user to check for WebP support. 941 * 942 * @param data 943 * Pointer to an int containing the current WebP support status for the 944 * client associated with the given user. This flag will be 0 if any user 945 * already checked has lacked WebP support, or 1 otherwise. 946 * 947 * @return 948 * Always NULL. 949 */ 950static void* __webp_support_callback(guac_user* user, void* data) { 951 952 int* webp_supported = (int*) data; 953 954 /* Check whether current user supports WebP */ 955 if (*webp_supported) 956 *webp_supported = guac_user_supports_webp(user); 957 958 return NULL; 959 960} 961#endif 962 963/** 964 * A callback function which is invoked by guac_client_owner_supports_msg() 965 * to determine if the owner of a client supports the "msg" instruction, 966 * returning zero if the user does not support the instruction or non-zero if 967 * the user supports it. 968 * 969 * @param user 970 * The guac_user that will be checked for "msg" instruction support. 971 * 972 * @param data 973 * Data provided to the callback. This value is never used within this 974 * callback. 975 * 976 * @return 977 * A non-zero integer if the provided user who owns the connection supports 978 * the "msg" instruction, or zero if the user does not. The integer is cast 979 * as a void*. 980 */ 981static void* guac_owner_supports_msg_callback(guac_user* user, void* data) { 982 983 return (void*) ((intptr_t) guac_user_supports_msg(user)); 984 985} 986 987int guac_client_owner_supports_msg(guac_client* client) { 988 989 return (int) ((intptr_t) guac_client_for_owner(client, guac_owner_supports_msg_callback, NULL)); 990 991} 992 993/** 994 * A callback function which is invoked by guac_client_owner_supports_required() 995 * to determine if the owner of a client supports the "required" instruction, 996 * returning zero if the user does not support the instruction or non-zero if 997 * the user supports it. 998 * 999 * @param user 1000 * The guac_user that will be checked for "required" instruction support. 1001 * 1002 * @param data 1003 * Data provided to the callback. This value is never used within this 1004 * callback. 1005 * 1006 * @return 1007 * A non-zero integer if the provided user who owns the connection supports 1008 * the "required" instruction, or zero if the user does not. The integer 1009 * is cast as a void*. 1010 */ 1011static void* guac_owner_supports_required_callback(guac_user* user, void* data) { 1012 1013 return (void*) ((intptr_t) guac_user_supports_required(user)); 1014 1015} 1016 1017int guac_client_owner_supports_required(guac_client* client) { 1018 1019 return (int) ((intptr_t) guac_client_for_owner(client, guac_owner_supports_required_callback, NULL)); 1020 1021} 1022 1023/** 1024 * A callback function that is invokved by guac_client_owner_notify_join() to 1025 * notify the owner of a connection that another user has joined the 1026 * connection, returning zero if the message is sent successfully, or non-zero 1027 * if an error occurs. 1028 * 1029 * @param user 1030 * The user to send the notification to, which will be the owner of the 1031 * connection. 1032 * 1033 * @param data 1034 * The data provided to the callback, which is the user that is joining the 1035 * connection. 1036 * 1037 * @return 1038 * Zero if the message is sent successfully to the owner, otherwise 1039 * non-zero, cast as a void*. 1040 */ 1041static void* guac_client_owner_notify_join_callback(guac_user* user, void* data) { 1042 1043 const guac_user* joiner = (const guac_user *) data; 1044 1045 if (user == NULL) 1046 return (void*) ((intptr_t) -1); 1047 1048 char* log_owner = "owner"; 1049 if (user->info.name != NULL) 1050 log_owner = (char *) user->info.name; 1051 1052 char* log_joiner = "anonymous"; 1053 char* send_joiner = ""; 1054 if (joiner->info.name != NULL) { 1055 log_joiner = (char *) joiner->info.name; 1056 send_joiner = (char *) joiner->info.name; 1057 } 1058 1059 guac_user_log(user, GUAC_LOG_DEBUG, "Notifying owner \"%s\" of \"%s\" joining.", 1060 log_owner, log_joiner); 1061 1062 /* Send user joined notification to owner. */ 1063 const char* args[] = { (const char*)joiner->user_id, (const char*)send_joiner, NULL }; 1064 return (void*) ((intptr_t) guac_protocol_send_msg(user->socket, GUAC_MESSAGE_USER_JOINED, args)); 1065 1066} 1067 1068int guac_client_owner_notify_join(guac_client* client, guac_user* joiner) { 1069 1070 /* Don't send msg instruction if client does not support it. */ 1071 if (!guac_client_owner_supports_msg(client)) { 1072 guac_client_log(client, GUAC_LOG_DEBUG, 1073 "Client does not support the \"msg\" instruction and " 1074 "will not be notified of the user joining the connection."); 1075 return -1; 1076 } 1077 1078 return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_notify_join_callback, joiner)); 1079 1080} 1081 1082/** 1083 * A callback function that is invokved by guac_client_owner_notify_leave() to 1084 * notify the owner of a connection that another user has left the connection, 1085 * returning zero if the message is sent successfully, or non-zero 1086 * if an error occurs. 1087 * 1088 * @param user 1089 * The user to send the notification to, which will be the owner of the 1090 * connection. 1091 * 1092 * @param data 1093 * The data provided to the callback, which is the user that is leaving the 1094 * connection. 1095 * 1096 * @return 1097 * Zero if the message is sent successfully to the owner, otherwise 1098 * non-zero, cast as a void*. 1099 */ 1100static void* guac_client_owner_notify_leave_callback(guac_user* user, void* data) { 1101 1102 const guac_user* quitter = (const guac_user *) data; 1103 1104 if (user == NULL) 1105 return (void*) ((intptr_t) -1); 1106 1107 char* log_owner = "owner"; 1108 if (user->info.name != NULL) 1109 log_owner = (char *) user->info.name; 1110 1111 char* log_quitter = "anonymous"; 1112 char* send_quitter = ""; 1113 if (quitter->info.name != NULL) { 1114 log_quitter = (char *) quitter->info.name; 1115 send_quitter = (char *) quitter->info.name; 1116 } 1117 1118 guac_user_log(user, GUAC_LOG_DEBUG, "Notifying owner \"%s\" of \"%s\" leaving.", 1119 log_owner, log_quitter); 1120 1121 /* Send user left notification to owner. */ 1122 const char* args[] = { (const char*)quitter->user_id, (const char*)send_quitter, NULL }; 1123 return (void*) ((intptr_t) guac_protocol_send_msg(user->socket, GUAC_MESSAGE_USER_LEFT, args)); 1124 1125} 1126 1127int guac_client_owner_notify_leave(guac_client* client, guac_user* quitter) { 1128 1129 /* Don't send msg instruction if client does not support it. */ 1130 if (!guac_client_owner_supports_msg(client)) { 1131 guac_client_log(client, GUAC_LOG_DEBUG, 1132 "Client does not support the \"msg\" instruction and " 1133 "will not be notified of the user leaving the connection."); 1134 return -1; 1135 } 1136 1137 return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_notify_leave_callback, quitter)); 1138 1139} 1140 1141int guac_client_supports_webp(guac_client* client) { 1142 1143#ifdef ENABLE_WEBP 1144 int webp_supported = 1; 1145 1146 /* WebP is supported for entire client only if each user supports it */ 1147 guac_client_foreach_user(client, __webp_support_callback, &webp_supported); 1148 1149 return webp_supported; 1150#else 1151 /* Support for WebP is completely absent */ 1152 return 0; 1153#endif 1154 1155} 1156