cscg24-guacamole

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

user-handlers.c (21882B)


      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 "guacamole/mem.h"
     23#include "guacamole/client.h"
     24#include "guacamole/object.h"
     25#include "guacamole/protocol.h"
     26#include "guacamole/stream.h"
     27#include "guacamole/string.h"
     28#include "guacamole/timestamp.h"
     29#include "guacamole/user.h"
     30#include "user-handlers.h"
     31
     32#include <inttypes.h>
     33#include <stdio.h>
     34#include <stdint.h>
     35#include <stdlib.h>
     36#include <string.h>
     37
     38/* Guacamole instruction handler map */
     39
     40__guac_instruction_handler_mapping __guac_instruction_handler_map[] = {
     41   {"sync",       __guac_handle_sync},
     42   {"touch",      __guac_handle_touch},
     43   {"mouse",      __guac_handle_mouse},
     44   {"key",        __guac_handle_key},
     45   {"clipboard",  __guac_handle_clipboard},
     46   {"disconnect", __guac_handle_disconnect},
     47   {"size",       __guac_handle_size},
     48   {"file",       __guac_handle_file},
     49   {"pipe",       __guac_handle_pipe},
     50   {"ack",        __guac_handle_ack},
     51   {"blob",       __guac_handle_blob},
     52   {"end",        __guac_handle_end},
     53   {"get",        __guac_handle_get},
     54   {"put",        __guac_handle_put},
     55   {"audio",      __guac_handle_audio},
     56   {"argv",       __guac_handle_argv},
     57   {"nop",        __guac_handle_nop},
     58   {NULL,         NULL}
     59};
     60
     61/* Guacamole handshake handler map */
     62
     63__guac_instruction_handler_mapping __guac_handshake_handler_map[] = {
     64    {"size",     __guac_handshake_size_handler},
     65    {"audio",    __guac_handshake_audio_handler},
     66    {"video",    __guac_handshake_video_handler},
     67    {"image",    __guac_handshake_image_handler},
     68    {"timezone", __guac_handshake_timezone_handler},
     69    {"name",     __guac_handshake_name_handler},
     70    {NULL,       NULL}
     71};
     72
     73/**
     74 * Parses a 64-bit integer from the given string. It is assumed that the string
     75 * will contain only decimal digits, with an optional leading minus sign.
     76 * The result of parsing a string which does not conform to this pattern is
     77 * undefined.
     78 *
     79 * @param str
     80 *     The string to parse, which must contain only decimal digits and an
     81 *     optional leading minus sign.
     82 *
     83 * @return
     84 *     The 64-bit integer value represented by the given string.
     85 */
     86static int64_t __guac_parse_int(const char* str) {
     87
     88    int sign = 1;
     89    int64_t num = 0;
     90
     91    for (; *str != '\0'; str++) {
     92
     93        if (*str == '-')
     94            sign = -sign;
     95        else
     96            num = num * 10 + (*str - '0');
     97
     98    }
     99
    100    return num * sign;
    101
    102}
    103
    104/* Guacamole instruction handlers */
    105
    106int __guac_handle_sync(guac_user* user, int argc, char** argv) {
    107
    108    int frame_duration;
    109
    110    guac_timestamp current = guac_timestamp_current();
    111    guac_timestamp timestamp = __guac_parse_int(argv[0]);
    112
    113    /* Error if timestamp is in future */
    114    if (timestamp > user->client->last_sent_timestamp)
    115        return -1;
    116
    117    /* Only update lag calculations if timestamp is sane */
    118    if (timestamp >= user->last_received_timestamp) {
    119
    120        /* Update stored timestamp */
    121        user->last_received_timestamp = timestamp;
    122
    123        /* Calculate length of frame, including network and processing lag */
    124        frame_duration = current - timestamp;
    125
    126        /* Update lag statistics if at least one frame has been rendered */
    127        if (user->last_frame_duration != 0) {
    128
    129            /* Calculate lag using the previous frame as a baseline */
    130            int processing_lag = frame_duration - user->last_frame_duration;
    131
    132            /* Adjust back to zero if cumulative error leads to a negative
    133             * value */
    134            if (processing_lag < 0)
    135                processing_lag = 0;
    136
    137            user->processing_lag = processing_lag;
    138
    139        }
    140
    141        /* Record baseline duration of frame by excluding lag */
    142        user->last_frame_duration = frame_duration - user->processing_lag;
    143
    144    }
    145
    146    /* Log received timestamp and calculated lag (at TRACE level only) */
    147    guac_user_log(user, GUAC_LOG_TRACE,
    148            "User confirmation of frame %" PRIu64 "ms received "
    149            "at %" PRIu64 "ms (processing_lag=%ims)",
    150            timestamp, current, user->processing_lag);
    151
    152    if (user->sync_handler)
    153        return user->sync_handler(user, timestamp);
    154    return 0;
    155}
    156
    157int __guac_handle_touch(guac_user* user, int argc, char** argv) {
    158    if (user->touch_handler)
    159        return user->touch_handler(
    160            user,
    161            atoi(argv[0]), /* id */
    162            atoi(argv[1]), /* x */
    163            atoi(argv[2]), /* y */
    164            atoi(argv[3]), /* x_radius */
    165            atoi(argv[4]), /* y_radius */
    166            atof(argv[5]), /* angle */
    167            atof(argv[6])  /* force */
    168        );
    169    return 0;
    170}
    171
    172int __guac_handle_mouse(guac_user* user, int argc, char** argv) {
    173    if (user->mouse_handler)
    174        return user->mouse_handler(
    175            user,
    176            atoi(argv[0]), /* x */
    177            atoi(argv[1]), /* y */
    178            atoi(argv[2])  /* mask */
    179        );
    180    return 0;
    181}
    182
    183int __guac_handle_key(guac_user* user, int argc, char** argv) {
    184    if (user->key_handler)
    185        return user->key_handler(
    186            user,
    187            atoi(argv[0]), /* keysym */
    188            atoi(argv[1])  /* pressed */
    189        );
    190    return 0;
    191}
    192
    193/**
    194 * Retrieves the existing user-level input stream having the given index. These
    195 * will be streams which were created by the remotely-connected user. If the
    196 * index is invalid or too large, this function will automatically respond with
    197 * an "ack" instruction containing an appropriate error code.
    198 *
    199 * @param user
    200 *     The user associated with the stream being retrieved.
    201 *
    202 * @param stream_index
    203 *     The index of the stream to retrieve.
    204 *
    205 * @return
    206 *     The stream associated with the given user and having the given index,
    207 *     or NULL if the index is invalid.
    208 */
    209static guac_stream* __get_input_stream(guac_user* user, int stream_index) {
    210
    211    /* Validate stream index */
    212    if (stream_index < 0 || stream_index >= GUAC_USER_MAX_STREAMS) {
    213
    214        guac_stream dummy_stream;
    215        dummy_stream.index = stream_index;
    216
    217        guac_protocol_send_ack(user->socket, &dummy_stream,
    218                "Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
    219        return NULL;
    220    }
    221
    222    return &(user->__input_streams[stream_index]);
    223
    224}
    225
    226/**
    227 * Retrieves the existing, in-progress (open) user-level input stream having
    228 * the given index. These will be streams which were created by the
    229 * remotely-connected user. If the index is invalid, too large, or the stream
    230 * is closed, this function will automatically respond with an "ack"
    231 * instruction containing an appropriate error code.
    232 *
    233 * @param user
    234 *     The user associated with the stream being retrieved.
    235 *
    236 * @param stream_index
    237 *     The index of the stream to retrieve.
    238 *
    239 * @return
    240 *     The in-progress (open)stream associated with the given user and having
    241 *     the given index, or NULL if the index is invalid or the stream is
    242 *     closed.
    243 */
    244static guac_stream* __get_open_input_stream(guac_user* user, int stream_index) {
    245
    246    guac_stream* stream = __get_input_stream(user, stream_index);
    247
    248    /* Fail if no such stream */
    249    if (stream == NULL)
    250        return NULL;
    251
    252    /* Validate initialization of stream */
    253    if (stream->index == GUAC_USER_CLOSED_STREAM_INDEX) {
    254
    255        guac_stream dummy_stream;
    256        dummy_stream.index = stream_index;
    257
    258        guac_protocol_send_ack(user->socket, &dummy_stream,
    259                "Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
    260        return NULL;
    261    }
    262
    263    return stream;
    264
    265}
    266
    267/**
    268 * Initializes and returns a new user-level input stream having the given
    269 * index, clearing any values that may have been assigned by a past use of the
    270 * underlying stream object storage. If the stream was already open, it will
    271 * first be closed and its end handlers invoked as if explicitly closed by the
    272 * user.
    273 *
    274 * @param user
    275 *     The user associated with the stream being initialized.
    276 *
    277 * @param stream_index
    278 *     The index of the stream to initialized.
    279 *
    280 * @return
    281 *     A new initialized user-level input stream having the given index, or
    282 *     NULL if the index is invalid.
    283 */
    284static guac_stream* __init_input_stream(guac_user* user, int stream_index) {
    285
    286    guac_stream* stream = __get_input_stream(user, stream_index);
    287
    288    /* Fail if no such stream */
    289    if (stream == NULL)
    290        return NULL;
    291
    292    /* Force end of previous stream if open */
    293    if (stream->index != GUAC_USER_CLOSED_STREAM_INDEX) {
    294
    295        /* Call stream handler if defined */
    296        if (stream->end_handler)
    297            stream->end_handler(user, stream);
    298
    299        /* Fall back to global handler if defined */
    300        else if (user->end_handler)
    301            user->end_handler(user, stream);
    302
    303    }
    304
    305    /* Initialize stream */
    306    stream->index = stream_index;
    307    stream->data = NULL;
    308    stream->ack_handler = NULL;
    309    stream->blob_handler = NULL;
    310    stream->end_handler = NULL;
    311
    312    return stream;
    313
    314}
    315
    316int __guac_handle_audio(guac_user* user, int argc, char** argv) {
    317
    318    /* Pull corresponding stream */
    319    int stream_index = atoi(argv[0]);
    320    guac_stream* stream = __init_input_stream(user, stream_index);
    321    if (stream == NULL)
    322        return 0;
    323
    324    /* If supported, call handler */
    325    if (user->audio_handler)
    326        return user->audio_handler(
    327            user,
    328            stream,
    329            argv[1] /* mimetype */
    330        );
    331
    332    /* Otherwise, abort */
    333    guac_protocol_send_ack(user->socket, stream,
    334            "Audio input unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
    335    return 0;
    336
    337}
    338
    339int __guac_handle_clipboard(guac_user* user, int argc, char** argv) {
    340
    341    /* Pull corresponding stream */
    342    int stream_index = atoi(argv[0]);
    343    guac_stream* stream = __init_input_stream(user, stream_index);
    344    if (stream == NULL)
    345        return 0;
    346
    347    /* If supported, call handler */
    348    if (user->clipboard_handler)
    349        return user->clipboard_handler(
    350            user,
    351            stream,
    352            argv[1] /* mimetype */
    353        );
    354
    355    /* Otherwise, abort */
    356    guac_protocol_send_ack(user->socket, stream,
    357            "Clipboard unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
    358    return 0;
    359
    360}
    361
    362int __guac_handle_size(guac_user* user, int argc, char** argv) {
    363    if (user->size_handler)
    364        return user->size_handler(
    365            user,
    366            atoi(argv[0]), /* width */
    367            atoi(argv[1])  /* height */
    368        );
    369    return 0;
    370}
    371
    372int __guac_handle_file(guac_user* user, int argc, char** argv) {
    373
    374    /* Pull corresponding stream */
    375    int stream_index = atoi(argv[0]);
    376    guac_stream* stream = __init_input_stream(user, stream_index);
    377    if (stream == NULL)
    378        return 0;
    379
    380    /* If supported, call handler */
    381    if (user->file_handler)
    382        return user->file_handler(
    383            user,
    384            stream,
    385            argv[1], /* mimetype */
    386            argv[2]  /* filename */
    387        );
    388
    389    /* Otherwise, abort */
    390    guac_protocol_send_ack(user->socket, stream,
    391            "File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
    392    return 0;
    393}
    394
    395int __guac_handle_pipe(guac_user* user, int argc, char** argv) {
    396
    397    /* Pull corresponding stream */
    398    int stream_index = atoi(argv[0]);
    399    guac_stream* stream = __init_input_stream(user, stream_index);
    400    if (stream == NULL)
    401        return 0;
    402
    403    /* If supported, call handler */
    404    if (user->pipe_handler)
    405        return user->pipe_handler(
    406            user,
    407            stream,
    408            argv[1], /* mimetype */
    409            argv[2]  /* name */
    410        );
    411
    412    /* Otherwise, abort */
    413    guac_protocol_send_ack(user->socket, stream,
    414            "Named pipes unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
    415    return 0;
    416}
    417
    418int __guac_handle_argv(guac_user* user, int argc, char** argv) {
    419
    420    /* Pull corresponding stream */
    421    int stream_index = atoi(argv[0]);
    422    guac_stream* stream = __init_input_stream(user, stream_index);
    423    if (stream == NULL)
    424        return 0;
    425
    426    /* If supported, call handler */
    427    if (user->argv_handler)
    428        return user->argv_handler(
    429            user,
    430            stream,
    431            argv[1], /* mimetype */
    432            argv[2]  /* name */
    433        );
    434
    435    /* Otherwise, abort */
    436    guac_protocol_send_ack(user->socket, stream,
    437            "Reconfiguring in-progress connections unsupported",
    438            GUAC_PROTOCOL_STATUS_UNSUPPORTED);
    439    return 0;
    440}
    441
    442int __guac_handle_ack(guac_user* user, int argc, char** argv) {
    443
    444    guac_stream* stream;
    445
    446    /* Parse stream index */
    447    int stream_index = atoi(argv[0]);
    448
    449    /* Ignore indices of client-level streams */
    450    if (stream_index % 2 != 0)
    451        return 0;
    452
    453    /* Determine index within user-level array of streams */
    454    stream_index /= 2;
    455
    456    /* Validate stream index */
    457    if (stream_index < 0 || stream_index >= GUAC_USER_MAX_STREAMS)
    458        return 0;
    459
    460    stream = &(user->__output_streams[stream_index]);
    461
    462    /* Validate initialization of stream */
    463    if (stream->index == GUAC_USER_CLOSED_STREAM_INDEX)
    464        return 0;
    465
    466    /* Call stream handler if defined */
    467    if (stream->ack_handler)
    468        return stream->ack_handler(user, stream, argv[1],
    469                atoi(argv[2]));
    470
    471    /* Fall back to global handler if defined */
    472    if (user->ack_handler)
    473        return user->ack_handler(user, stream, argv[1],
    474                atoi(argv[2]));
    475
    476    return 0;
    477}
    478
    479int __guac_handle_blob(guac_user* user, int argc, char** argv) {
    480
    481    int stream_index = atoi(argv[0]);
    482    guac_stream* stream = __get_open_input_stream(user, stream_index);
    483
    484    /* Fail if no such stream */
    485    if (stream == NULL)
    486        return 0;
    487
    488    /* Call stream handler if defined */
    489    if (stream->blob_handler) {
    490        int length = guac_protocol_decode_base64(argv[1]);
    491        return stream->blob_handler(user, stream, argv[1],
    492            length);
    493    }
    494
    495    /* Fall back to global handler if defined */
    496    if (user->blob_handler) {
    497        int length = guac_protocol_decode_base64(argv[1]);
    498        return user->blob_handler(user, stream, argv[1],
    499            length);
    500    }
    501
    502    guac_protocol_send_ack(user->socket, stream,
    503            "File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
    504    return 0;
    505}
    506
    507int __guac_handle_end(guac_user* user, int argc, char** argv) {
    508
    509    int result = 0;
    510    int stream_index = atoi(argv[0]);
    511    guac_stream* stream = __get_open_input_stream(user, stream_index);
    512
    513    /* Fail if no such stream */
    514    if (stream == NULL)
    515        return 0;
    516
    517    /* Call stream handler if defined */
    518    if (stream->end_handler)
    519        result = stream->end_handler(user, stream);
    520
    521    /* Fall back to global handler if defined */
    522    else if (user->end_handler)
    523        result = user->end_handler(user, stream);
    524
    525    /* Mark stream as closed */
    526    stream->index = GUAC_USER_CLOSED_STREAM_INDEX;
    527    return result;
    528}
    529
    530int __guac_handle_get(guac_user* user, int argc, char** argv) {
    531
    532    guac_object* object;
    533
    534    /* Validate object index */
    535    int object_index = atoi(argv[0]);
    536    if (object_index < 0 || object_index >= GUAC_USER_MAX_OBJECTS)
    537        return 0;
    538
    539    object = &(user->__objects[object_index]);
    540
    541    /* Validate initialization of object */
    542    if (object->index == GUAC_USER_UNDEFINED_OBJECT_INDEX)
    543        return 0;
    544
    545    /* Call object handler if defined */
    546    if (object->get_handler)
    547        return object->get_handler(
    548            user,
    549            object,
    550            argv[1] /* name */
    551        );
    552
    553    /* Fall back to global handler if defined */
    554    if (user->get_handler)
    555        return user->get_handler(
    556            user,
    557            object,
    558            argv[1] /* name */
    559        );
    560
    561    return 0;
    562}
    563
    564int __guac_handle_put(guac_user* user, int argc, char** argv) {
    565
    566    guac_object* object;
    567
    568    /* Validate object index */
    569    int object_index = atoi(argv[0]);
    570    if (object_index < 0 || object_index >= GUAC_USER_MAX_OBJECTS)
    571        return 0;
    572
    573    object = &(user->__objects[object_index]);
    574
    575    /* Validate initialization of object */
    576    if (object->index == GUAC_USER_UNDEFINED_OBJECT_INDEX)
    577        return 0;
    578
    579    /* Pull corresponding stream */
    580    int stream_index = atoi(argv[1]);
    581    guac_stream* stream = __init_input_stream(user, stream_index);
    582    if (stream == NULL)
    583        return 0;
    584
    585    /* Call object handler if defined */
    586    if (object->put_handler)
    587        return object->put_handler(
    588            user,
    589            object, 
    590            stream,
    591            argv[2], /* mimetype */
    592            argv[3]  /* name */
    593        );
    594
    595    /* Fall back to global handler if defined */
    596    if (user->put_handler)
    597        return user->put_handler(
    598            user,
    599            object,
    600            stream,
    601            argv[2], /* mimetype */
    602            argv[3]  /* name */
    603        );
    604
    605    /* Otherwise, abort */
    606    guac_protocol_send_ack(user->socket, stream,
    607            "Object write unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
    608    return 0;
    609}
    610
    611int __guac_handle_nop(guac_user* user, int argc, char** argv) {
    612    guac_user_log(user, GUAC_LOG_TRACE,
    613            "Received nop instruction");
    614    return 0;
    615}
    616
    617int __guac_handle_disconnect(guac_user* user, int argc, char** argv) {
    618    guac_user_stop(user);
    619    return 0;
    620}
    621
    622/* Guacamole handshake handler functions. */
    623
    624int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) {
    625    
    626    /* Validate size of instruction. */
    627    if (argc < 2) {
    628        guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" "
    629                "instruction lacked required arguments.");
    630        return 1;
    631    }
    632    
    633    /* Parse optimal screen dimensions from size instruction */
    634    user->info.optimal_width  = atoi(argv[0]);
    635    user->info.optimal_height = atoi(argv[1]);
    636
    637    /* If DPI given, set the user resolution */
    638    if (argc >= 3)
    639        user->info.optimal_resolution = atoi(argv[2]);
    640
    641    /* Otherwise, use a safe default for rough backwards compatibility */
    642    else
    643        user->info.optimal_resolution = 96;
    644    
    645    return 0;
    646    
    647}
    648
    649int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) {
    650
    651    guac_free_mimetypes((char **) user->info.audio_mimetypes);
    652    
    653    /* Store audio mimetypes */
    654    user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
    655    
    656    return 0;
    657    
    658}
    659
    660int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) {
    661
    662    guac_free_mimetypes((char **) user->info.video_mimetypes);
    663    
    664    /* Store video mimetypes */
    665    user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
    666    
    667    return 0;
    668    
    669}
    670
    671int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) {
    672    
    673    guac_free_mimetypes((char **) user->info.image_mimetypes);
    674    
    675    /* Store image mimetypes */
    676    user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
    677    
    678    return 0;
    679    
    680}
    681
    682int __guac_handshake_name_handler(guac_user* user, int argc, char** argv) {
    683
    684    /* Free any past value for the user's name */
    685    guac_mem_free_const(user->info.name);
    686
    687    /* If a value is provided for the name, copy it into guac_user. */
    688    if (argc > 0 && strcmp(argv[0], ""))
    689        user->info.name = (const char*) guac_strdup(argv[0]);
    690
    691    /* No or empty value was provided, so make sure this is NULLed out. */
    692    else
    693        user->info.name = NULL;
    694
    695    return 0;
    696
    697}
    698
    699int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) {
    700    
    701    /* Free any past value */
    702    guac_mem_free_const(user->info.timezone);
    703    
    704    /* Store timezone, if present */
    705    if (argc > 0 && strcmp(argv[0], ""))
    706        user->info.timezone = (const char*) guac_strdup(argv[0]);
    707    
    708    else
    709        user->info.timezone = NULL;
    710    
    711    return 0;
    712    
    713}
    714
    715char** guac_copy_mimetypes(char** mimetypes, int count) {
    716
    717    int i;
    718
    719    /* Allocate sufficient space for NULL-terminated array of mimetypes */
    720    char** mimetypes_copy = guac_mem_alloc(sizeof(char*),
    721            guac_mem_ckd_add_or_die(count, 1));
    722
    723    /* Copy each provided mimetype */
    724    for (i = 0; i < count; i++)
    725        mimetypes_copy[i] = guac_strdup(mimetypes[i]);
    726
    727    /* Terminate with NULL */
    728    mimetypes_copy[count] = NULL;
    729
    730    return mimetypes_copy;
    731
    732}
    733
    734void guac_free_mimetypes(char** mimetypes) {
    735
    736    if (mimetypes == NULL)
    737        return;
    738    
    739    char** current_mimetype = mimetypes;
    740
    741    /* Free all strings within NULL-terminated mimetype array */
    742    while (*current_mimetype != NULL) {
    743        guac_mem_free(*current_mimetype);
    744        current_mimetype++;
    745    }
    746
    747    /* Free the array itself, now that its contents have been freed */
    748    guac_mem_free(mimetypes);
    749
    750}
    751
    752int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map,
    753        guac_user* user, const char* opcode, int argc, char** argv) {
    754
    755    /* For each defined instruction */
    756    __guac_instruction_handler_mapping* current = map;
    757    while (current->opcode != NULL) {
    758
    759        /* If recognized, call handler */
    760        if (strcmp(opcode, current->opcode) == 0)
    761            return current->handler(user, argc, argv);
    762
    763        current++;
    764    }
    765
    766    /* If unrecognized, log and ignore */
    767    guac_user_log(user, GUAC_LOG_DEBUG, "Handler not found for \"%s\"",
    768            opcode);
    769    return 0;
    770
    771}
    772