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