cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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