cscg24-guacamole

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

socket-nest.c (9868B)


      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/protocol.h"
     24#include "guacamole/socket.h"
     25#include "guacamole/unicode.h"
     26
     27#include <stddef.h>
     28#include <stdlib.h>
     29#include <string.h>
     30#include <unistd.h>
     31
     32/**
     33 * The maximum number of bytes to buffer before sending a "nest" instruction.
     34 * As some of the 8 KB space available for each instruction will be taken up by
     35 * the "nest" opcode and other parameters, and 1 KB will be more than enough
     36 * space for that extra data, this space is reduced to an even 7 KB.
     37 */
     38#define GUAC_SOCKET_NEST_BUFFER_SIZE 7168
     39
     40/**
     41 * Internal data associated with an open socket which writes via a series of
     42 * "nest" instructions to some underlying, parent socket.
     43 */
     44typedef struct guac_socket_nest_data {
     45
     46    /**
     47     * The underlying socket which should be used to write "nest" instructions.
     48     */
     49    guac_socket* parent;
     50
     51    /**
     52     * The arbitrary index of the nested socket, assigned at time of
     53     * allocation.
     54     */
     55    int index;
     56
     57    /**
     58     * The number of bytes currently in the main write buffer.
     59     */
     60    int written;
     61
     62    /**
     63     * The main write buffer. Bytes written go here before being flushed
     64     * as nest instructions. Space is included for the null terminator
     65     * required by guac_protocol_send_nest().
     66     */
     67    char buffer[GUAC_SOCKET_NEST_BUFFER_SIZE];
     68
     69    /**
     70     * Lock which is acquired when an instruction is being written, and
     71     * released when the instruction is finished being written.
     72     */
     73    pthread_mutex_t socket_lock;
     74
     75    /**
     76     * Lock which protects access to the internal buffer of this socket,
     77     * guaranteeing atomicity of writes and flushes.
     78     */
     79    pthread_mutex_t buffer_lock;
     80
     81} guac_socket_nest_data;
     82
     83/**
     84 * Flushes the contents of the output buffer of the given socket immediately,
     85 * without first locking access to the output buffer. This function must ONLY
     86 * be called if the buffer lock has already been acquired.
     87 *
     88 * @param socket
     89 *     The guac_socket to flush.
     90 *
     91 * @return
     92 *     Zero if the flush operation was successful, non-zero otherwise.
     93 */
     94static ssize_t guac_socket_nest_flush(guac_socket* socket) {
     95
     96    guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
     97
     98    /* Flush remaining bytes in buffer */
     99    if (data->written > 0) {
    100
    101        /* Determine length of buffer containing complete UTF-8 characters
    102         * (buffer may end with a partial, multi-byte character) */
    103        int length = 0;
    104        while (length < data->written)
    105            length += guac_utf8_charsize(*(data->buffer + length));
    106
    107        /* Add null terminator, preserving overwritten character for later
    108         * restoration (guac_protocol_send_nest() requires null-terminated
    109         * strings) */
    110        char overwritten = data->buffer[length];
    111        data->buffer[length] = '\0';
    112
    113        /* Write ALL bytes in buffer as nest instruction */
    114        int retval = guac_protocol_send_nest(data->parent, data->index, data->buffer);
    115
    116        /* Restore original value overwritten by null terminator */
    117        data->buffer[length] = overwritten;
    118
    119        if (retval)
    120            return 1;
    121
    122        /* Shift any remaining data to beginning of buffer */
    123        memcpy(data->buffer, data->buffer + length, data->written - length);
    124        data->written -= length;
    125
    126    }
    127
    128    return 0;
    129
    130}
    131
    132/**
    133 * Flushes the internal buffer of the given guac_socket, writing all data
    134 * to the underlying socket using "nest" instructions.
    135 *
    136 * @param socket
    137 *     The guac_socket to flush.
    138 *
    139 * @return
    140 *     Zero if the flush operation was successful, non-zero otherwise.
    141 */
    142static ssize_t guac_socket_nest_flush_handler(guac_socket* socket) {
    143
    144    int retval;
    145    guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
    146
    147    /* Acquire exclusive access to buffer */
    148    pthread_mutex_lock(&(data->buffer_lock));
    149
    150    /* Flush contents of buffer */
    151    retval = guac_socket_nest_flush(socket);
    152
    153    /* Relinquish exclusive access to buffer */
    154    pthread_mutex_unlock(&(data->buffer_lock));
    155
    156    return retval;
    157
    158}
    159
    160/**
    161 * Writes the contents of the buffer to the output buffer of the given socket,
    162 * flushing the output buffer as necessary, without first locking access to the
    163 * output buffer. This function must ONLY be called if the buffer lock has
    164 * already been acquired.
    165 *
    166 * @param socket
    167 *     The guac_socket to write the given buffer to.
    168 *
    169 * @param buf
    170 *     The buffer to write to the given socket.
    171 *
    172 * @param count
    173 *     The number of bytes in the given buffer.
    174 *
    175 * @return
    176 *     The number of bytes written, or a negative value if an error occurs
    177 *     during write.
    178 */
    179static ssize_t guac_socket_nest_write_buffered(guac_socket* socket,
    180        const void* buf, size_t count) {
    181
    182    size_t original_count = count;
    183    const char* current = buf;
    184    guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
    185
    186    /* Append to buffer, flush if necessary */
    187    while (count > 0) {
    188
    189        int chunk_size;
    190
    191        /* Calculate space remaining, including one extra byte for the null
    192         * terminator added upon flush */
    193        int remaining = sizeof(data->buffer) - data->written - 1;
    194
    195        /* If no space left in buffer, flush and retry */
    196        if (remaining == 0) {
    197
    198            /* Abort if error occurs during flush */
    199            if (guac_socket_nest_flush(socket))
    200                return -1;
    201
    202            /* Retry buffer append */
    203            continue;
    204
    205        }
    206
    207        /* Calculate size of chunk to be written to buffer */
    208        chunk_size = count;
    209        if (chunk_size > remaining)
    210            chunk_size = remaining;
    211
    212        /* Update output buffer */
    213        memcpy(data->buffer + data->written, current, chunk_size);
    214        data->written += chunk_size;
    215
    216        /* Update provided buffer */
    217        current += chunk_size;
    218        count   -= chunk_size;
    219
    220    }
    221
    222    /* All bytes have been written, possibly some to the internal buffer */
    223    return original_count;
    224
    225}
    226
    227/**
    228 * Appends the provided data to the internal buffer for future writing. The
    229 * actual write attempt will occur only upon flush, or when the internal buffer
    230 * is full.
    231 *
    232 * @param socket
    233 *     The guac_socket being write to.
    234 *
    235 * @param buf
    236 *     The arbitrary buffer containing the data to be written.
    237 *
    238 * @param count
    239 *     The number of bytes contained within the buffer.
    240 *
    241 * @return
    242 *     The number of bytes written, or -1 if an error occurs.
    243 */
    244static ssize_t guac_socket_nest_write_handler(guac_socket* socket,
    245        const void* buf, size_t count) {
    246
    247    int retval;
    248    guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
    249    
    250    /* Acquire exclusive access to buffer */
    251    pthread_mutex_lock(&(data->buffer_lock));
    252
    253    /* Write provided data to buffer */
    254    retval = guac_socket_nest_write_buffered(socket, buf, count);
    255
    256    /* Relinquish exclusive access to buffer */
    257    pthread_mutex_unlock(&(data->buffer_lock));
    258
    259    return retval;
    260
    261}
    262
    263/**
    264 * Frees all implementation-specific data associated with the given socket, but
    265 * not the socket object itself.
    266 *
    267 * @param socket
    268 *     The guac_socket whose associated data should be freed.
    269 *
    270 * @return
    271 *     Zero if the data was successfully freed, non-zero otherwise. This
    272 *     implementation always succeeds, and will always return zero.
    273 */
    274static int guac_socket_nest_free_handler(guac_socket* socket) {
    275
    276    /* Free associated data */
    277    guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
    278    guac_mem_free(data);
    279
    280    return 0;
    281
    282}
    283
    284/**
    285 * Acquires exclusive access to the given socket.
    286 *
    287 * @param socket
    288 *     The guac_socket to which exclusive access is required.
    289 */
    290static void guac_socket_nest_lock_handler(guac_socket* socket) {
    291
    292    guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
    293
    294    /* Acquire exclusive access to socket */
    295    pthread_mutex_lock(&(data->socket_lock));
    296
    297}
    298
    299/**
    300 * Relinquishes exclusive access to the given socket.
    301 *
    302 * @param socket
    303 *     The guac_socket to which exclusive access is no longer required.
    304 */
    305static void guac_socket_nest_unlock_handler(guac_socket* socket) {
    306
    307    guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
    308
    309    /* Relinquish exclusive access to socket */
    310    pthread_mutex_unlock(&(data->socket_lock));
    311
    312}
    313
    314guac_socket* guac_socket_nest(guac_socket* parent, int index) {
    315
    316    /* Allocate socket and associated data */
    317    guac_socket* socket = guac_socket_alloc();
    318    guac_socket_nest_data* data = guac_mem_alloc(sizeof(guac_socket_nest_data));
    319
    320    /* Store nested socket details as socket data */
    321    data->parent = parent;
    322    data->index = index;
    323    socket->data = data;
    324
    325    /* Set relevant handlers */
    326    socket->write_handler  = guac_socket_nest_write_handler;
    327    socket->lock_handler   = guac_socket_nest_lock_handler;
    328    socket->unlock_handler = guac_socket_nest_unlock_handler;
    329    socket->flush_handler  = guac_socket_nest_flush_handler;
    330    socket->free_handler   = guac_socket_nest_free_handler;
    331
    332    return socket;
    333
    334}
    335