cscg24-guacamole

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

socket-broadcast.c (12411B)


      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/error.h"
     25#include "guacamole/socket.h"
     26#include "guacamole/user.h"
     27
     28#include <pthread.h>
     29#include <stdlib.h>
     30
     31/**
     32 * A function that will broadcast arbitrary data to a subset of users for
     33 * the provided client, using the provided user callback for any user-specific
     34 * operations.
     35 *
     36 * @param client
     37 *     The guac_client associated with the users to broadcast to.
     38 *
     39 * @param callback
     40 *     A callback that should be invoked with each broadcasted user.
     41 *
     42 * @param data
     43 *     Arbitrary data that may be used to broadcast to the subset of users.
     44 */
     45typedef void guac_socket_broadcast_handler(
     46        guac_client* client, guac_user_callback* callback, void* data);
     47
     48/**
     49 * Data associated with an open socket which writes to a subset of connected
     50 * users of a particular guac_client.
     51 */
     52typedef struct guac_socket_broadcast_data {
     53
     54    /**
     55     * The guac_client whose connected users should receive all instructions
     56     * written to this socket.
     57     */
     58    guac_client* client;
     59
     60    /**
     61     * Lock which is acquired when an instruction is being written, and
     62     * released when the instruction is finished being written.
     63     */
     64    pthread_mutex_t socket_lock;
     65
     66    /**
     67     * The function to broadcast
     68     */
     69    guac_socket_broadcast_handler* broadcast_handler;
     70
     71} guac_socket_broadcast_data;
     72
     73/**
     74 * Single chunk of data, to be broadcast to all users.
     75 */
     76typedef struct __write_chunk {
     77
     78    /**
     79     * The buffer to write.
     80     */
     81    const void* buffer;
     82
     83    /**
     84     * The number of bytes in the buffer.
     85     */
     86    size_t length;
     87
     88} __write_chunk;
     89
     90/**
     91 * Callback which handles read requests on the broadcast socket. This callback
     92 * always fails, as the broadcast socket is write-only; it cannot be read.
     93 *
     94 * @param socket
     95 *     The broadcast socket to read from.
     96 *
     97 * @param buf
     98 *     The buffer into which data should be read.
     99 *
    100 * @param count
    101 *     The number of bytes to attempt to read.
    102 *
    103 * @return
    104 *     The number of bytes read, or -1 if an error occurs. This implementation
    105 *     always returns -1, as the broadcast socket is write-only and cannot be
    106 *     read.
    107 */
    108static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket,
    109        void* buf, size_t count) {
    110
    111    /* Broadcast socket reads are not allowed */
    112    return -1;
    113
    114}
    115
    116/**
    117 * Callback invoked by the broadcast handler which write a given chunk of
    118 * data to that user's socket. If the write attempt fails, the user is
    119 * signalled to stop with guac_user_stop().
    120 *
    121 * @param user
    122 *     The user that the chunk of data should be written to.
    123 *
    124 * @param data
    125 *     A pointer to a __write_chunk which describes the data to be written.
    126 *
    127 * @return
    128 *     Always NULL.
    129 */
    130static void* __write_chunk_callback(guac_user* user, void* data) {
    131
    132    __write_chunk* chunk = (__write_chunk*) data;
    133
    134    /* Attempt write, disconnect on failure */
    135    if (guac_socket_write(user->socket, chunk->buffer, chunk->length))
    136        guac_user_stop(user);
    137
    138    return NULL;
    139
    140}
    141
    142/**
    143 * Socket write handler which operates on each of the sockets of all connected
    144 * users. This write handler will always succeed, but any failing user-specific
    145 * writes will invoke guac_user_stop() on the failing user.
    146 *
    147 * @param socket
    148 *     The socket to which the given data must be written.
    149 *
    150 * @param buf
    151 *     The buffer containing the data to write.
    152 *
    153 * @param count
    154 *     The number of bytes to attempt to write from the given buffer.
    155 *
    156 * @return
    157 *     The number of bytes written, or -1 if an error occurs. This handler will
    158 *     always succeed, and thus will always return the exact number of bytes
    159 *     specified by count.
    160 */
    161static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket,
    162        const void* buf, size_t count) {
    163
    164    guac_socket_broadcast_data* data =
    165        (guac_socket_broadcast_data*) socket->data;
    166
    167    /* Build chunk */
    168    __write_chunk chunk;
    169    chunk.buffer = buf;
    170    chunk.length = count;
    171
    172    /* Broadcast chunk to the users */
    173    data->broadcast_handler(data->client, __write_chunk_callback, &chunk);
    174
    175    return count;
    176
    177}
    178
    179/**
    180 * Callback which is invoked by the broadcast handler to flush all
    181 * pending data on the given user's socket. If an error occurs while flushing
    182 * a user's socket, that user is signalled to stop with guac_user_stop().
    183 *
    184 * @param user
    185 *     The user whose socket should be flushed.
    186 *
    187 * @param data
    188 *     Arbitrary data passed to the broadcast handler. This is not needed
    189 *     by this callback, and should be left as NULL.
    190 *
    191 * @return
    192 *     Always NULL.
    193 */
    194static void* __flush_callback(guac_user* user, void* data) {
    195
    196    /* Attempt flush, disconnect on failure */
    197    if (guac_socket_flush(user->socket))
    198        guac_user_stop(user);
    199
    200    return NULL;
    201
    202}
    203
    204/**
    205 * Socket flush handler which operates on each of the sockets of all connected
    206 * users. This flush handler will always succeed, but any failing user-specific
    207 * flush will invoke guac_user_stop() on the failing user.
    208 *
    209 * @param socket
    210 *     The broadcast socket to flush.
    211 *
    212 * @return
    213 *     Zero if the flush operation succeeds, non-zero if the operation fails.
    214 *     This handler will always succeed, and thus will always return zero.
    215 */
    216static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) {
    217
    218    guac_socket_broadcast_data* data =
    219        (guac_socket_broadcast_data*) socket->data;
    220
    221    /* Flush the users */
    222    data->broadcast_handler(data->client, __flush_callback, NULL);
    223
    224    return 0;
    225
    226}
    227
    228/**
    229 * Callback which is invoked by the broadcast handler to lock the given
    230 * user's socket in preparation for the beginning of a Guacamole protocol
    231 * instruction.
    232 *
    233 * @param user
    234 *     The user whose socket should be locked.
    235 *
    236 * @param data
    237 *     Arbitrary data passed to the broadcast handler. This is not needed
    238 *     by this callback, and should be left as NULL.
    239 *
    240 * @return
    241 *     Always NULL.
    242 */
    243static void* __lock_callback(guac_user* user, void* data) {
    244
    245    /* Lock socket */
    246    guac_socket_instruction_begin(user->socket);
    247
    248    return NULL;
    249
    250}
    251
    252/**
    253 * Socket lock handler which acquires the socket locks of all connected users.
    254 * Socket-level locks are acquired in preparation for the beginning of a new
    255 * Guacamole instruction to ensure that parallel writes are only interleaved at
    256 * instruction boundaries.
    257 *
    258 * @param socket
    259 *     The broadcast socket to lock.
    260 */
    261static void __guac_socket_broadcast_lock_handler(guac_socket* socket) {
    262
    263    guac_socket_broadcast_data* data =
    264        (guac_socket_broadcast_data*) socket->data;
    265
    266    /* Acquire exclusive access to socket */
    267    pthread_mutex_lock(&(data->socket_lock));
    268
    269    /* Lock sockets of the users */
    270    data->broadcast_handler(data->client, __lock_callback, NULL);
    271
    272}
    273
    274/**
    275 * Callback which is invoked by the broadcast handler to unlock the given
    276 * user's socket at the end of a Guacamole protocol instruction.
    277 *
    278 * @param user
    279 *     The user whose socket should be unlocked.
    280 *
    281 * @param data
    282 *     Arbitrary data passed to the broadcast handler. This is not needed
    283 *     by this callback, and should be left as NULL.
    284 *
    285 * @return
    286 *     Always NULL.
    287 */
    288static void* __unlock_callback(guac_user* user, void* data) {
    289
    290    /* Unlock socket */
    291    guac_socket_instruction_end(user->socket);
    292
    293    return NULL;
    294
    295}
    296
    297/**
    298 * Socket unlock handler which releases the socket locks of all connected users.
    299 * Socket-level locks are released after a Guacamole instruction has finished
    300 * being written.
    301 *
    302 * @param socket
    303 *     The broadcast socket to unlock.
    304 */
    305static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) {
    306
    307    guac_socket_broadcast_data* data =
    308        (guac_socket_broadcast_data*) socket->data;
    309
    310    /* Unlock sockets of all users */
    311    data->broadcast_handler(data->client, __unlock_callback, NULL);
    312
    313    /* Relinquish exclusive access to socket */
    314    pthread_mutex_unlock(&(data->socket_lock));
    315
    316}
    317
    318/**
    319 * Callback which handles select operations on the broadcast socket, waiting
    320 * for data to become available such that the next read operation will not
    321 * block. This callback always fails, as the broadcast socket is write-only; it
    322 * cannot be read.
    323 *
    324 * @param socket
    325 *     The broadcast socket to wait for.
    326 *
    327 * @param usec_timeout
    328 *     The maximum amount of time to wait for data, in microseconds, or -1 to
    329 *     potentially wait forever.
    330 *
    331 * @return
    332 *     A positive value on success, zero if the timeout elapsed and no data is
    333 *     available, or a negative value if an error occurs. This implementation
    334 *     always returns -1, as the broadcast socket is write-only and cannot be
    335 *     read.
    336 */
    337static int __guac_socket_broadcast_select_handler(guac_socket* socket,
    338        int usec_timeout) {
    339
    340    /* Selecting the broadcast socket is not possible */
    341    return -1;
    342
    343}
    344
    345/**
    346 * Frees all implementation-specific data associated with the given socket, but
    347 * not the socket object itself.
    348 *
    349 * @param socket
    350 *     The guac_socket whose associated data should be freed.
    351 *
    352 * @return
    353 *     Zero if the data was successfully freed, non-zero otherwise. This
    354 *     implementation always succeeds, and will always return zero.
    355 */
    356static int __guac_socket_broadcast_free_handler(guac_socket* socket) {
    357
    358    guac_socket_broadcast_data* data =
    359        (guac_socket_broadcast_data*) socket->data;
    360
    361    /* Destroy locks */
    362    pthread_mutex_destroy(&(data->socket_lock));
    363
    364    guac_mem_free(data);
    365    return 0;
    366
    367}
    368
    369/**
    370 * Construct and return a socket that will broadcast to the users given by
    371 * by the provided broadcast handler.
    372 *
    373 * @param client
    374 *     The client who's users are being broadcast to.
    375 *
    376 * @param broadcast_handler
    377 *     The handler that will peform the broadcast against a subset of users
    378 *     of the provided client.
    379 *
    380 * @return
    381 *     The newly constructed broadcast socket
    382 */
    383static guac_socket* __guac_socket_init(
    384        guac_client* client, guac_socket_broadcast_handler* broadcast_handler) {
    385
    386    pthread_mutexattr_t lock_attributes;
    387
    388    /* Allocate socket and associated data */
    389    guac_socket* socket = guac_socket_alloc();
    390    guac_socket_broadcast_data* data =
    391        guac_mem_alloc(sizeof(guac_socket_broadcast_data));
    392
    393    /* Set the provided broadcast handler */
    394    data->broadcast_handler = broadcast_handler;
    395
    396    /* Store client as socket data */
    397    data->client = client;
    398    socket->data = data;
    399
    400    pthread_mutexattr_init(&lock_attributes);
    401    pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
    402
    403    /* Init lock */
    404    pthread_mutex_init(&(data->socket_lock), &lock_attributes);
    405
    406    /* Set read/write handlers */
    407    socket->read_handler   = __guac_socket_broadcast_read_handler;
    408    socket->write_handler  = __guac_socket_broadcast_write_handler;
    409    socket->select_handler = __guac_socket_broadcast_select_handler;
    410    socket->flush_handler  = __guac_socket_broadcast_flush_handler;
    411    socket->lock_handler   = __guac_socket_broadcast_lock_handler;
    412    socket->unlock_handler = __guac_socket_broadcast_unlock_handler;
    413    socket->free_handler   = __guac_socket_broadcast_free_handler;
    414
    415    return socket;
    416
    417}
    418
    419guac_socket* guac_socket_broadcast(guac_client* client) {
    420
    421    /* Broadcast to all connected non-pending users*/
    422    return __guac_socket_init(client, guac_client_foreach_user);
    423
    424}
    425
    426guac_socket* guac_socket_broadcast_pending(guac_client* client) {
    427
    428    /* Broadcast to all connected pending users*/
    429    return __guac_socket_init(client, guac_client_foreach_pending_user);
    430
    431}
    432