diff --git a/Makefile.am b/Makefile.am index 9b5ab08f..6d4e8676 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,32 +37,30 @@ DIST_SUBDIRS = \ SUBDIRS = \ src/libguac \ - src/common \ - src/guacd \ tests if ENABLE_COMMON_SSH -SUBDIRS += src/common-ssh +#SUBDIRS += src/common-ssh endif if ENABLE_TERMINAL -SUBDIRS += src/terminal +#SUBDIRS += src/terminal endif if ENABLE_RDP -SUBDIRS += src/protocols/rdp +#SUBDIRS += src/protocols/rdp endif if ENABLE_SSH -SUBDIRS += src/protocols/ssh +#SUBDIRS += src/protocols/ssh endif if ENABLE_TELNET -SUBDIRS += src/protocols/telnet +#SUBDIRS += src/protocols/telnet endif if ENABLE_VNC -SUBDIRS += src/protocols/vnc +#SUBDIRS += src/protocols/vnc endif EXTRA_DIST = \ diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 039f23e6..e18ff338 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -38,16 +38,15 @@ libguacinc_HEADERS = \ guacamole/error.h \ guacamole/error-types.h \ guacamole/hash.h \ - guacamole/instruction-constants.h \ - guacamole/instruction.h \ - guacamole/instruction-types.h \ guacamole/layer.h \ guacamole/layer-types.h \ guacamole/object.h \ guacamole/object-types.h \ + guacamole/parser-constants.h \ + guacamole/parser.h \ + guacamole/parser-types.h \ guacamole/plugin-constants.h \ guacamole/plugin.h \ - guacamole/plugin-types.h \ guacamole/pool.h \ guacamole/pool-types.h \ guacamole/protocol.h \ @@ -60,26 +59,30 @@ libguacinc_HEADERS = \ guacamole/stream-types.h \ guacamole/timestamp.h \ guacamole/timestamp-types.h \ - guacamole/unicode.h + guacamole/unicode.h \ + guacamole/user.h \ + guacamole/user-constants.h \ + guacamole/user-fntypes.h \ + guacamole/user-types.h noinst_HEADERS = \ - client-handlers.h \ + id.h \ encode-jpeg.h \ encode-png.h \ palette.h \ + user-handlers.h \ raw_encoder.h libguac_la_SOURCES = \ audio.c \ client.c \ - client-handlers.c \ encode-jpeg.c \ encode-png.c \ error.c \ hash.c \ - instruction.c \ + id.c \ palette.c \ - plugin.c \ + parser.c \ pool.c \ protocol.c \ raw_encoder.c \ @@ -87,7 +90,9 @@ libguac_la_SOURCES = \ socket-fd.c \ socket-nest.c \ timestamp.c \ - unicode.c + unicode.c \ + user.c \ + user-handlers.c # Compile WebP support if available if ENABLE_WEBP diff --git a/src/libguac/audio.c b/src/libguac/audio.c index 8d340be8..3ff7615e 100644 --- a/src/libguac/audio.c +++ b/src/libguac/audio.c @@ -28,51 +28,74 @@ #include #include #include +#include #include #include +/** + * Assigns a new audio encoder to the given guac_audio_stream based on the + * audio mimetypes declared as supported by the given user. If no audio encoder + * can be found, no new audio encoder is assigned, and the existing encoder is + * left untouched (if any). + * + * @param owner + * The user whose supported audio mimetypes should determine the audio + * encoder selected. It is expected that this user will be the owner of + * the connection. + * + * @param data + * The guac_audio_stream to which the new encoder should be assigned. + * Existing properties set on this audio stream, such as the bits per + * sample, may affect the encoder chosen. + * + * @return + * The assigned audio encoder. If no new audio encoder can be assigned, + * this will be the currently-assigned audio encoder (which may be NULL). + */ +static void* guac_audio_assign_encoder(guac_user* owner, void* data) { + + int i; + + guac_audio_stream* audio = (guac_audio_stream*) data; + int bps = audio->bps; + + /* If there is no owner, do not attempt to assign a new encoder */ + if (owner == NULL) + return audio->encoder; + + /* For each supported mimetype, check for an associated encoder */ + for (i=0; owner->info.audio_mimetypes[i] != NULL; i++) { + + const char* mimetype = owner->info.audio_mimetypes[i]; + + /* If 16-bit raw audio is supported, done. */ + if (bps == 16 && strcmp(mimetype, raw16_encoder->mimetype) == 0) { + audio->encoder = raw16_encoder; + break; + } + + /* If 8-bit raw audio is supported, done. */ + if (bps == 8 && strcmp(mimetype, raw8_encoder->mimetype) == 0) { + audio->encoder = raw8_encoder; + break; + } + + } /* end for each mimetype */ + + /* Return assigned encoder, if any */ + return audio->encoder; + +} + guac_audio_stream* guac_audio_stream_alloc(guac_client* client, guac_audio_encoder* encoder, int rate, int channels, int bps) { guac_audio_stream* audio; - /* Choose an encoding if not specified */ - if (encoder == NULL) { - - int i; - - /* For each supported mimetype, check for an associated encoder */ - for (i=0; client->info.audio_mimetypes[i] != NULL; i++) { - - const char* mimetype = client->info.audio_mimetypes[i]; - - /* If 16-bit raw audio is supported, done. */ - if (bps == 16 && strcmp(mimetype, raw16_encoder->mimetype) == 0) { - encoder = raw16_encoder; - break; - } - - /* If 8-bit raw audio is supported, done. */ - if (bps == 8 && strcmp(mimetype, raw8_encoder->mimetype) == 0) { - encoder = raw8_encoder; - break; - } - - } /* end for each mimetype */ - - /* If still no encoder could be found, fail */ - if (encoder == NULL) - return NULL; - - } - /* Allocate stream */ audio = (guac_audio_stream*) calloc(1, sizeof(guac_audio_stream)); audio->client = client; - - /* Assign encoder */ - audio->encoder = encoder; audio->stream = guac_client_alloc_stream(client); /* Load PCM properties */ @@ -80,6 +103,13 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client, audio->channels = channels; audio->bps = bps; + /* Assign encoder for owner, abort if no encoder can be found */ + if (!guac_client_for_owner(client, guac_audio_assign_encoder, audio)) { + guac_client_free_stream(client, audio->stream); + free(audio); + return NULL; + } + /* Call handler, if defined */ if (audio->encoder->begin_handler) audio->encoder->begin_handler(audio); @@ -118,6 +148,16 @@ void guac_audio_stream_reset(guac_audio_stream* audio, } +void guac_audio_stream_add_user(guac_audio_stream* audio, guac_user* user) { + + guac_audio_encoder* encoder = audio->encoder; + + /* Notify encoder that a new user is present */ + if (encoder->join_handler) + encoder->join_handler(audio, user); + +} + void guac_audio_stream_free(guac_audio_stream* audio) { /* Flush stream encoding */ diff --git a/src/libguac/client-handlers.c b/src/libguac/client-handlers.c deleted file mode 100644 index fc965aa8..00000000 --- a/src/libguac/client-handlers.c +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "config.h" - -#include "client.h" -#include "client-handlers.h" -#include "instruction.h" -#include "object.h" -#include "protocol.h" -#include "stream.h" -#include "timestamp.h" - -#include -#include -#include - -/* Guacamole instruction handler map */ - -__guac_instruction_handler_mapping __guac_instruction_handler_map[] = { - {"sync", __guac_handle_sync}, - {"mouse", __guac_handle_mouse}, - {"key", __guac_handle_key}, - {"clipboard", __guac_handle_clipboard}, - {"disconnect", __guac_handle_disconnect}, - {"size", __guac_handle_size}, - {"file", __guac_handle_file}, - {"pipe", __guac_handle_pipe}, - {"ack", __guac_handle_ack}, - {"blob", __guac_handle_blob}, - {"end", __guac_handle_end}, - {"get", __guac_handle_get}, - {"put", __guac_handle_put}, - {NULL, NULL} -}; - -int64_t __guac_parse_int(const char* str) { - - int sign = 1; - int64_t num = 0; - - for (; *str != '\0'; str++) { - - if (*str == '-') - sign = -sign; - else - num = num * 10 + (*str - '0'); - - } - - return num * sign; - -} - -/* Guacamole instruction handlers */ - -int __guac_handle_sync(guac_client* client, guac_instruction* instruction) { - guac_timestamp timestamp = __guac_parse_int(instruction->argv[0]); - - /* Error if timestamp is in future */ - if (timestamp > client->last_sent_timestamp) - return -1; - - client->last_received_timestamp = timestamp; - return 0; -} - -int __guac_handle_mouse(guac_client* client, guac_instruction* instruction) { - if (client->mouse_handler) - return client->mouse_handler( - client, - atoi(instruction->argv[0]), /* x */ - atoi(instruction->argv[1]), /* y */ - atoi(instruction->argv[2]) /* mask */ - ); - return 0; -} - -int __guac_handle_key(guac_client* client, guac_instruction* instruction) { - if (client->key_handler) - return client->key_handler( - client, - atoi(instruction->argv[0]), /* keysym */ - atoi(instruction->argv[1]) /* pressed */ - ); - return 0; -} - -static guac_stream* __get_input_stream(guac_client* client, int stream_index) { - - /* Validate stream index */ - if (stream_index < 0 || stream_index >= GUAC_CLIENT_MAX_STREAMS) { - - guac_stream dummy_stream; - dummy_stream.index = stream_index; - - guac_protocol_send_ack(client->socket, &dummy_stream, - "Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); - return NULL; - } - - return &(client->__input_streams[stream_index]); - -} - -static guac_stream* __get_open_input_stream(guac_client* client, int stream_index) { - - guac_stream* stream = __get_input_stream(client, stream_index); - - /* Fail if no such stream */ - if (stream == NULL) - return NULL; - - /* Validate initialization of stream */ - if (stream->index == GUAC_CLIENT_CLOSED_STREAM_INDEX) { - - guac_stream dummy_stream; - dummy_stream.index = stream_index; - - guac_protocol_send_ack(client->socket, &dummy_stream, - "Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); - return NULL; - } - - return stream; - -} - -static guac_stream* __init_input_stream(guac_client* client, int stream_index) { - - guac_stream* stream = __get_input_stream(client, stream_index); - - /* Fail if no such stream */ - if (stream == NULL) - return NULL; - - /* Initialize stream */ - stream->index = stream_index; - stream->data = NULL; - stream->ack_handler = NULL; - stream->blob_handler = NULL; - stream->end_handler = NULL; - - return stream; - -} - -int __guac_handle_clipboard(guac_client* client, guac_instruction* instruction) { - - /* Pull corresponding stream */ - int stream_index = atoi(instruction->argv[0]); - guac_stream* stream = __init_input_stream(client, stream_index); - if (stream == NULL) - return 0; - - /* If supported, call handler */ - if (client->clipboard_handler) - return client->clipboard_handler( - client, - stream, - instruction->argv[1] /* mimetype */ - ); - - /* Otherwise, abort */ - guac_protocol_send_ack(client->socket, stream, - "Clipboard unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); - return 0; - -} - -int __guac_handle_size(guac_client* client, guac_instruction* instruction) { - if (client->size_handler) - return client->size_handler( - client, - atoi(instruction->argv[0]), /* width */ - atoi(instruction->argv[1]) /* height */ - ); - return 0; -} - -int __guac_handle_file(guac_client* client, guac_instruction* instruction) { - - /* Pull corresponding stream */ - int stream_index = atoi(instruction->argv[0]); - guac_stream* stream = __init_input_stream(client, stream_index); - if (stream == NULL) - return 0; - - /* If supported, call handler */ - if (client->file_handler) - return client->file_handler( - client, - stream, - instruction->argv[1], /* mimetype */ - instruction->argv[2] /* filename */ - ); - - /* Otherwise, abort */ - guac_protocol_send_ack(client->socket, stream, - "File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); - return 0; -} - -int __guac_handle_pipe(guac_client* client, guac_instruction* instruction) { - - /* Pull corresponding stream */ - int stream_index = atoi(instruction->argv[0]); - guac_stream* stream = __init_input_stream(client, stream_index); - if (stream == NULL) - return 0; - - /* If supported, call handler */ - if (client->pipe_handler) - return client->pipe_handler( - client, - stream, - instruction->argv[1], /* mimetype */ - instruction->argv[2] /* name */ - ); - - /* Otherwise, abort */ - guac_protocol_send_ack(client->socket, stream, - "Named pipes unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); - return 0; -} - -int __guac_handle_ack(guac_client* client, guac_instruction* instruction) { - - guac_stream* stream; - - /* Validate stream index */ - int stream_index = atoi(instruction->argv[0]); - if (stream_index < 0 || stream_index >= GUAC_CLIENT_MAX_STREAMS) - return 0; - - stream = &(client->__output_streams[stream_index]); - - /* Validate initialization of stream */ - if (stream->index == GUAC_CLIENT_CLOSED_STREAM_INDEX) - return 0; - - /* Call stream handler if defined */ - if (stream->ack_handler) - return stream->ack_handler(client, stream, instruction->argv[1], - atoi(instruction->argv[2])); - - /* Fall back to global handler if defined */ - if (client->ack_handler) - return client->ack_handler(client, stream, instruction->argv[1], - atoi(instruction->argv[2])); - - return 0; -} - -int __guac_handle_blob(guac_client* client, guac_instruction* instruction) { - - int stream_index = atoi(instruction->argv[0]); - guac_stream* stream = __get_open_input_stream(client, stream_index); - - /* Fail if no such stream */ - if (stream == NULL) - return 0; - - /* Call stream handler if defined */ - if (stream->blob_handler) { - int length = guac_protocol_decode_base64(instruction->argv[1]); - return stream->blob_handler(client, stream, instruction->argv[1], - length); - } - - /* Fall back to global handler if defined */ - if (client->blob_handler) { - int length = guac_protocol_decode_base64(instruction->argv[1]); - return client->blob_handler(client, stream, instruction->argv[1], - length); - } - - guac_protocol_send_ack(client->socket, stream, - "File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); - return 0; -} - -int __guac_handle_end(guac_client* client, guac_instruction* instruction) { - - int result = 0; - int stream_index = atoi(instruction->argv[0]); - guac_stream* stream = __get_open_input_stream(client, stream_index); - - /* Fail if no such stream */ - if (stream == NULL) - return 0; - - /* Call stream handler if defined */ - if (stream->end_handler) - result = stream->end_handler(client, stream); - - /* Fall back to global handler if defined */ - if (client->end_handler) - result = client->end_handler(client, stream); - - /* Mark stream as closed */ - stream->index = GUAC_CLIENT_CLOSED_STREAM_INDEX; - return result; -} - -int __guac_handle_get(guac_client* client, guac_instruction* instruction) { - - guac_object* object; - - /* Validate object index */ - int object_index = atoi(instruction->argv[0]); - if (object_index < 0 || object_index >= GUAC_CLIENT_MAX_OBJECTS) - return 0; - - object = &(client->__objects[object_index]); - - /* Validate initialization of object */ - if (object->index == GUAC_CLIENT_UNDEFINED_OBJECT_INDEX) - return 0; - - /* Call object handler if defined */ - if (object->get_handler) - return object->get_handler(client, object, - instruction->argv[1] /* name */ - ); - - /* Fall back to global handler if defined */ - if (client->get_handler) - return client->get_handler(client, object, - instruction->argv[1] /* name */ - ); - - return 0; -} - -int __guac_handle_put(guac_client* client, guac_instruction* instruction) { - - guac_object* object; - - /* Validate object index */ - int object_index = atoi(instruction->argv[0]); - if (object_index < 0 || object_index >= GUAC_CLIENT_MAX_OBJECTS) - return 0; - - object = &(client->__objects[object_index]); - - /* Validate initialization of object */ - if (object->index == GUAC_CLIENT_UNDEFINED_OBJECT_INDEX) - return 0; - - /* Pull corresponding stream */ - int stream_index = atoi(instruction->argv[1]); - guac_stream* stream = __init_input_stream(client, stream_index); - if (stream == NULL) - return 0; - - /* Call object handler if defined */ - if (object->put_handler) - return object->put_handler(client, object, stream, - instruction->argv[2], /* mimetype */ - instruction->argv[3] /* name */ - ); - - /* Fall back to global handler if defined */ - if (client->put_handler) - return client->put_handler(client, object, stream, - instruction->argv[2], /* mimetype */ - instruction->argv[3] /* name */ - ); - - /* Otherwise, abort */ - guac_protocol_send_ack(client->socket, stream, - "Object write unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); - return 0; -} - -int __guac_handle_disconnect(guac_client* client, guac_instruction* instruction) { - guac_client_stop(client); - return 0; -} - diff --git a/src/libguac/client.c b/src/libguac/client.c index 949bc26f..7cdf4da4 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -23,29 +23,22 @@ #include "config.h" #include "client.h" -#include "client-handlers.h" #include "encode-jpeg.h" #include "encode-png.h" +#include "encode-webp.h" #include "error.h" -#include "instruction.h" +#include "id.h" #include "layer.h" -#include "object.h" #include "pool.h" +#include "plugin.h" #include "protocol.h" #include "socket.h" #include "stream.h" #include "timestamp.h" +#include "user.h" -#ifdef ENABLE_WEBP -#include "encode-webp.h" -#endif - -#ifdef HAVE_OSSP_UUID_H -#include -#else -#include -#endif - +#include +#include #include #include #include @@ -57,6 +50,23 @@ guac_layer __GUAC_DEFAULT_LAYER = { const guac_layer* GUAC_DEFAULT_LAYER = &__GUAC_DEFAULT_LAYER; +/** + * Single chunk of data, to be broadcast to all users. + */ +typedef struct __write_chunk { + + /** + * The buffer to write. + */ + const void* buffer; + + /** + * The number of bytes in the buffer. + */ + size_t length; + +} __write_chunk; + guac_layer* guac_client_alloc_layer(guac_client* client) { /* Init new layer */ @@ -109,9 +119,9 @@ guac_stream* guac_client_alloc_stream(guac_client* client) { /* Allocate stream */ stream_index = guac_pool_next_int(client->__stream_pool); - /* Initialize stream */ + /* Initialize stream with odd index (even indices are user-level) */ allocd_stream = &(client->__output_streams[stream_index]); - allocd_stream->index = stream_index; + allocd_stream->index = (stream_index * 2) + 1; allocd_stream->data = NULL; allocd_stream->ack_handler = NULL; allocd_stream->blob_handler = NULL; @@ -124,107 +134,262 @@ guac_stream* guac_client_alloc_stream(guac_client* client) { void guac_client_free_stream(guac_client* client, guac_stream* stream) { /* Release index to pool */ - guac_pool_free_int(client->__stream_pool, stream->index); + guac_pool_free_int(client->__stream_pool, (stream->index - 1) / 2); /* Mark stream as closed */ stream->index = GUAC_CLIENT_CLOSED_STREAM_INDEX; } -guac_object* guac_client_alloc_object(guac_client* client) { +/** + * Callback which handles read requests on the broadcast socket. This callback + * always fails, as the broadcast socket is write-only; it cannot be read. + * + * @param socket + * The broadcast socket to read from. + * + * @param buf + * The buffer into which data should be read. + * + * @param count + * The number of bytes to attempt to read. + * + * @return + * The number of bytes read, or -1 if an error occurs. This implementation + * always returns -1, as the broadcast socket is write-only and cannot be + * read. + */ +static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket, + void* buf, size_t count) { - guac_object* allocd_object; - int object_index; - - /* Refuse to allocate beyond maximum */ - if (client->__object_pool->active == GUAC_CLIENT_MAX_OBJECTS) - return NULL; - - /* Allocate object */ - object_index = guac_pool_next_int(client->__object_pool); - - /* Initialize object */ - allocd_object = &(client->__objects[object_index]); - allocd_object->index = object_index; - allocd_object->data = NULL; - allocd_object->get_handler = NULL; - allocd_object->put_handler = NULL; - - return allocd_object; - -} - -void guac_client_free_object(guac_client* client, guac_object* object) { - - /* Release index to pool */ - guac_pool_free_int(client->__object_pool, object->index); - - /* Mark object as undefined */ - object->index = GUAC_CLIENT_UNDEFINED_OBJECT_INDEX; + /* Broadcast socket reads are not allowed */ + return -1; } /** - * Returns a newly allocated string containing a guaranteed-unique connection - * identifier string which is 37 characters long and begins with a '$' - * character. If an error occurs, NULL is returned, and no memory is - * allocated. + * Callback invoked by guac_client_foreach_user() which write a given chunk of + * data to that user's socket. If the write attempt fails, the user is + * signalled to stop with guac_user_stop(). + * + * @param user + * The user that the chunk of data should be written to. + * + * @param data + * A pointer to a __write_chunk which describes the data to be written. + * + * @return + * Always NULL. */ -static char* __guac_generate_connection_id() { +static void* __write_chunk_callback(guac_user* user, void* data) { - char* buffer; - char* identifier; - size_t identifier_length; + __write_chunk* chunk = (__write_chunk*) data; - uuid_t* uuid; + /* Attempt write, disconnect on failure */ + if (guac_socket_write(user->socket, chunk->buffer, chunk->length)) + guac_user_stop(user); - /* Attempt to create UUID object */ - if (uuid_create(&uuid) != UUID_RC_OK) { - guac_error = GUAC_STATUS_NO_MEMORY; - guac_error_message = "Could not allocate memory for UUID"; - return NULL; - } + return NULL; - /* Generate random UUID */ - if (uuid_make(uuid, UUID_MAKE_V4) != UUID_RC_OK) { - uuid_destroy(uuid); - guac_error = GUAC_STATUS_NO_MEMORY; - guac_error_message = "UUID generation failed"; - return NULL; - } +} - /* Allocate buffer for future formatted ID */ - buffer = malloc(UUID_LEN_STR + 2); - if (buffer == NULL) { - uuid_destroy(uuid); - guac_error = GUAC_STATUS_NO_MEMORY; - guac_error_message = "Could not allocate memory for connection ID"; - return NULL; - } +/** + * Socket write handler which operates on each of the sockets of all connected + * users. This write handler will always succeed, but any failing user-specific + * writes will invoke guac_user_stop() on the failing user. + * + * @param socket + * The socket to which the given data must be written. + * + * @param buf + * The buffer containing the data to write. + * + * @param count + * The number of bytes to attempt to write from the given buffer. + * + * @return + * The number of bytes written, or -1 if an error occurs. This handler will + * always succeed, and thus will always return the exact number of bytes + * specified by count. + */ +static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket, + const void* buf, size_t count) { - identifier = &(buffer[1]); - identifier_length = UUID_LEN_STR + 1; + guac_client* client = (guac_client*) socket->data; - /* Build connection ID from UUID */ - if (uuid_export(uuid, UUID_FMT_STR, &identifier, &identifier_length) != UUID_RC_OK) { - free(buffer); - uuid_destroy(uuid); - guac_error = GUAC_STATUS_INTERNAL_ERROR; - guac_error_message = "Conversion of UUID to connection ID failed"; - return NULL; - } + /* Build chunk */ + __write_chunk chunk; + chunk.buffer = buf; + chunk.length = count; - uuid_destroy(uuid); + /* Broadcast chunk to all users */ + guac_client_foreach_user(client, __write_chunk_callback, &chunk); - buffer[0] = '$'; - buffer[UUID_LEN_STR + 1] = '\0'; - return buffer; + return count; + +} + +/** + * Callback which is invoked by guac_client_foreach_user() to flush all + * pending data on the given user's socket. If an error occurs while flushing + * a user's socket, that user is signalled to stop with guac_user_stop(). + * + * @param user + * The user whose socket should be flushed. + * + * @param data + * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * by this callback, and should be left as NULL. + * + * @return + * Always NULL. + */ +static void* __flush_callback(guac_user* user, void* data) { + + /* Attempt flush, disconnect on failure */ + if (guac_socket_flush(user->socket)) + guac_user_stop(user); + + return NULL; + +} + +/** + * Socket flush handler which operates on each of the sockets of all connected + * users. This flush handler will always succeed, but any failing user-specific + * flush will invoke guac_user_stop() on the failing user. + * + * @param socket + * The broadcast socket to flush. + * + * @return + * Zero if the flush operation succeeds, non-zero if the operation fails. + * This handler will always succeed, and thus will always return zero. + */ +static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) { + + guac_client* client = (guac_client*) socket->data; + + /* Flush all users */ + guac_client_foreach_user(client, __flush_callback, NULL); + + return 0; + +} + +/** + * Callback which is invoked by guac_client_foreach_user() to lock the given + * user's socket in preparation for the beginning of a Guacamole protocol + * instruction. + * + * @param user + * The user whose socket should be locked. + * + * @param data + * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * by this callback, and should be left as NULL. + * + * @return + * Always NULL. + */ +static void* __lock_callback(guac_user* user, void* data) { + + /* Lock socket */ + guac_socket_instruction_begin(user->socket); + + return NULL; + +} + +/** + * Socket lock handler which acquires the socket locks of all connected users. + * Socket-level locks are acquired in preparation for the beginning of a new + * Guacamole instruction to ensure that parallel writes are only interleaved at + * instruction boundaries. + * + * @param socket + * The broadcast socket to lock. + */ +static void __guac_socket_broadcast_lock_handler(guac_socket* socket) { + + guac_client* client = (guac_client*) socket->data; + + /* Lock sockets of all users */ + guac_client_foreach_user(client, __lock_callback, NULL); + +} + +/** + * Callback which is invoked by guac_client_foreach_user() to unlock the given + * user's socket at the end of a Guacamole protocol instruction. + * + * @param user + * The user whose socket should be unlocked. + * + * @param data + * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * by this callback, and should be left as NULL. + * + * @return + * Always NULL. + */ +static void* __unlock_callback(guac_user* user, void* data) { + + /* Unlock socket */ + guac_socket_instruction_end(user->socket); + + return NULL; + +} + +/** + * Socket unlock handler which releases the socket locks of all connected users. + * Socket-level locks are released after a Guacamole instruction has finished + * being written. + * + * @param socket + * The broadcast socket to unlock. + */ +static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) { + + guac_client* client = (guac_client*) socket->data; + + /* Unlock sockets of all users */ + guac_client_foreach_user(client, __unlock_callback, NULL); + +} + +/** + * Callback which handles select operations on the broadcast socket, waiting + * for data to become available such that the next read operation will not + * block. This callback always fails, as the broadcast socket is write-only; it + * cannot be read. + * + * @param socket + * The broadcast socket to wait for. + * + * @param usec_timeout + * The maximum amount of time to wait for data, in microseconds, or -1 to + * potentially wait forever. + * + * @return + * A positive value on success, zero if the timeout elapsed and no data is + * available, or a negative value if an error occurs. This implementation + * always returns -1, as the broadcast socket is write-only and cannot be + * read. + */ +static int __guac_socket_broadcast_select_handler(guac_socket* socket, + int usec_timeout) { + + /* Selecting the broadcast socket is not possible */ + return -1; } guac_client* guac_client_alloc() { int i; + pthread_rwlockattr_t lock_attributes; /* Allocate new client */ guac_client* client = malloc(sizeof(guac_client)); @@ -237,13 +402,11 @@ guac_client* guac_client_alloc() { /* Init new client */ memset(client, 0, sizeof(guac_client)); - client->last_received_timestamp = - client->last_sent_timestamp = guac_timestamp_current(); - client->state = GUAC_CLIENT_RUNNING; + client->last_sent_timestamp = guac_timestamp_current(); /* Generate ID */ - client->connection_id = __guac_generate_connection_id(); + client->connection_id = guac_generate_id(GUAC_CLIENT_ID_PREFIX); if (client->connection_id == NULL) { free(client); return NULL; @@ -257,21 +420,30 @@ guac_client* guac_client_alloc() { client->__stream_pool = guac_pool_alloc(0); /* Initialize streams */ - client->__input_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS); client->__output_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS); for (i=0; i__input_streams[i].index = GUAC_CLIENT_CLOSED_STREAM_INDEX; client->__output_streams[i].index = GUAC_CLIENT_CLOSED_STREAM_INDEX; } - /* Allocate object pool */ - client->__object_pool = guac_pool_alloc(0); - /* Initialize objects */ - client->__objects = malloc(sizeof(guac_object) * GUAC_CLIENT_MAX_OBJECTS); - for (i=0; i__objects[i].index = GUAC_CLIENT_UNDEFINED_OBJECT_INDEX; + /* Init locks */ + pthread_rwlockattr_init(&lock_attributes); + pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); + + pthread_rwlock_init(&(client->__users_lock), &lock_attributes); + + /* Set up socket to broadcast to all users */ + guac_socket* socket = guac_socket_alloc(); + client->socket = socket; + socket->data = client; + + socket->read_handler = __guac_socket_broadcast_read_handler; + socket->write_handler = __guac_socket_broadcast_write_handler; + socket->select_handler = __guac_socket_broadcast_select_handler; + socket->flush_handler = __guac_socket_broadcast_flush_handler; + socket->lock_handler = __guac_socket_broadcast_lock_handler; + socket->unlock_handler = __guac_socket_broadcast_unlock_handler; return client; @@ -279,6 +451,10 @@ guac_client* guac_client_alloc() { void guac_client_free(guac_client* client) { + /* Remove all users */ + while (client->__users != NULL) + guac_client_remove_user(client, client->__users); + if (client->free_handler) { /* FIXME: Errors currently ignored... */ @@ -291,37 +467,19 @@ void guac_client_free(guac_client* client) { guac_pool_free(client->__layer_pool); /* Free streams */ - free(client->__input_streams); free(client->__output_streams); /* Free stream pool */ guac_pool_free(client->__stream_pool); - /* Free objects */ - free(client->__objects); - - /* Free object pool */ - guac_pool_free(client->__object_pool); - - free(client); -} - -int guac_client_handle_instruction(guac_client* client, guac_instruction* instruction) { - - /* For each defined instruction */ - __guac_instruction_handler_mapping* current = __guac_instruction_handler_map; - while (current->opcode != NULL) { - - /* If recognized, call handler */ - if (strcmp(instruction->opcode, current->opcode) == 0) - return current->handler(client, instruction); - - current++; + /* Close associated plugin */ + if (client->__plugin_handle != NULL) { + if (dlclose(client->__plugin_handle)) + guac_client_log(client, GUAC_LOG_ERROR, "Unable to close plugin: %s", dlerror()); } - /* If unrecognized, ignore */ - return 0; - + pthread_rwlock_destroy(&(client->__users_lock)); + free(client); } void vguac_client_log(guac_client* client, guac_client_log_level level, @@ -381,6 +539,203 @@ void guac_client_abort(guac_client* client, guac_protocol_status status, } +int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** argv) { + + int retval = 0; + + pthread_rwlock_wrlock(&(client->__users_lock)); + + /* Call handler, if defined */ + if (client->join_handler) + retval = client->join_handler(user, argc, argv); + + /* Add to list if join was successful */ + if (retval == 0) { + + user->__prev = NULL; + user->__next = client->__users; + + if (client->__users != NULL) + client->__users->__prev = user; + + client->__users = user; + client->connected_users++; + + /* Update owner pointer if user is owner */ + if (user->owner) + client->__owner = user; + + } + + pthread_rwlock_unlock(&(client->__users_lock)); + + return retval; + +} + +void guac_client_remove_user(guac_client* client, guac_user* user) { + + pthread_rwlock_wrlock(&(client->__users_lock)); + + /* Call handler, if defined */ + if (user->leave_handler) + user->leave_handler(user); + else if (client->leave_handler) + client->leave_handler(user); + + /* Update prev / head */ + if (user->__prev != NULL) + user->__prev->__next = user->__next; + else + client->__users = user->__next; + + /* Update next */ + if (user->__next != NULL) + user->__next->__prev = user->__prev; + + client->connected_users--; + + /* Update owner pointer if user was owner */ + if (user->owner) + client->__owner = NULL; + + pthread_rwlock_unlock(&(client->__users_lock)); + +} + +void guac_client_foreach_user(guac_client* client, guac_user_callback* callback, void* data) { + + guac_user* current; + + pthread_rwlock_rdlock(&(client->__users_lock)); + + /* Call function on each user */ + current = client->__users; + while (current != NULL) { + callback(current, data); + current = current->__next; + } + + pthread_rwlock_unlock(&(client->__users_lock)); + +} + +void* guac_client_for_owner(guac_client* client, guac_user_callback* callback, + void* data) { + + void* retval; + + pthread_rwlock_rdlock(&(client->__users_lock)); + + /* Invoke callback with current owner */ + retval = callback(client->__owner, data); + + pthread_rwlock_unlock(&(client->__users_lock)); + + /* Return value from callback */ + return retval; + +} + +int guac_client_end_frame(guac_client* client) { + + /* Update and send timestamp */ + client->last_sent_timestamp = guac_timestamp_current(); + return guac_protocol_send_sync(client->socket, client->last_sent_timestamp); + +} + +/** + * Empty NULL-terminated array of argument names. + */ +const char* __GUAC_CLIENT_NO_ARGS[] = { NULL }; + +int guac_client_load_plugin(guac_client* client, const char* protocol) { + + /* Reference to dlopen()'d plugin */ + void* client_plugin_handle; + + /* Pluggable client */ + char protocol_lib[GUAC_PROTOCOL_LIBRARY_LIMIT] = + GUAC_PROTOCOL_LIBRARY_PREFIX; + + /* Type-pun for the sake of dlsym() - cannot typecast a void* to a function + * pointer otherwise */ + union { + guac_client_init_handler* client_init; + void* obj; + } alias; + + /* Add protocol and .so suffix to protocol_lib */ + strncat(protocol_lib, protocol, GUAC_PROTOCOL_NAME_LIMIT-1); + strcat(protocol_lib, GUAC_PROTOCOL_LIBRARY_SUFFIX); + + /* Load client plugin */ + client_plugin_handle = dlopen(protocol_lib, RTLD_LAZY); + if (!client_plugin_handle) { + guac_error = GUAC_STATUS_NOT_FOUND; + guac_error_message = dlerror(); + return -1; + } + + dlerror(); /* Clear errors */ + + /* Get init function */ + alias.obj = dlsym(client_plugin_handle, "guac_client_init"); + + /* Fail if cannot find guac_client_init */ + if (dlerror() != NULL) { + guac_error = GUAC_STATUS_INTERNAL_ERROR; + guac_error_message = dlerror(); + return -1; + } + + /* Init client */ + client->args = __GUAC_CLIENT_NO_ARGS; + client->__plugin_handle = client_plugin_handle; + + return alias.client_init(client); + +} + +/** + * Updates the provided approximate processing lag, taking into account the + * processing lag of the given user. + * + * @param user + * The guac_user to use to update the approximate processing lag. + * + * @param data + * Pointer to an int containing the current approximate processing lag. + * The int will be updated according to the processing lag of the given + * user. + * + * @return + * Always NULL. + */ +static void* __calculate_lag(guac_user* user, void* data) { + + int* processing_lag = (int*) data; + + /* Simply find maximum */ + if (user->processing_lag > *processing_lag) + *processing_lag = user->processing_lag; + + return NULL; + +} + +int guac_client_get_processing_lag(guac_client* client) { + + int processing_lag = 0; + + /* Approximate the processing lag of all users */ + guac_client_foreach_user(client, __calculate_lag, &processing_lag); + + return processing_lag; + +} + void guac_client_stream_png(guac_client* client, guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int x, int y, cairo_surface_t* surface) { @@ -448,25 +803,45 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket, } +#ifdef ENABLE_WEBP +/** + * Callback which is invoked by guac_client_supports_webp() for each user + * associated with the given client, thus updating an overall support flag + * describing the WebP support state for the client as a whole. + * + * @param user + * The user to check for WebP support. + * + * @param data + * Pointer to an int containing the current WebP support status for the + * client associated with the given user. This flag will be 0 if any user + * already checked has lacked WebP support, or 1 otherwise. + * + * @return + * Always NULL. + */ +static void* __webp_support_callback(guac_user* user, void* data) { + + int* webp_supported = (int*) data; + + /* Check whether current user supports WebP */ + if (*webp_supported) + *webp_supported = guac_user_supports_webp(user); + + return NULL; + +} +#endif + int guac_client_supports_webp(guac_client* client) { #ifdef ENABLE_WEBP - char** mimetype = client->info.image_mimetypes; + int webp_supported = 1; - /* Search for WebP mimetype in list of supported image mimetypes */ - while (*mimetype != NULL) { + /* WebP is supported for entire client only if each user supports it */ + guac_client_foreach_user(client, __webp_support_callback, &webp_supported); - /* If WebP mimetype found, no need to search further */ - if (strcmp(*mimetype, "image/webp") == 0) - return 1; - - /* Next mimetype */ - mimetype++; - - } - - /* Client does not support WebP */ - return 0; + return webp_supported; #else /* Support for WebP is completely absent */ return 0; diff --git a/src/libguac/guacamole/audio-fntypes.h b/src/libguac/guacamole/audio-fntypes.h index 7880eac7..718120da 100644 --- a/src/libguac/guacamole/audio-fntypes.h +++ b/src/libguac/guacamole/audio-fntypes.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Glyptodon LLC + * Copyright (C) 2016 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,8 +20,8 @@ * THE SOFTWARE. */ -#ifndef __GUAC_AUDIO_FNTYPES_H -#define __GUAC_AUDIO_FNTYPES_H +#ifndef GUAC_AUDIO_FNTYPES_H +#define GUAC_AUDIO_FNTYPES_H /** * Function type definitions related to simple streaming audio. @@ -30,24 +30,59 @@ */ #include "audio-types.h" +#include "user-types.h" /** * Handler which is called when the audio stream is opened. + * + * @param audio + * The audio stream being opened. */ typedef void guac_audio_encoder_begin_handler(guac_audio_stream* audio); /** * Handler which is called when the audio stream needs to be flushed. + * + * @param audio + * The audio stream being flushed. */ typedef void guac_audio_encoder_flush_handler(guac_audio_stream* audio); /** * Handler which is called when the audio stream is closed. + * + * @param audio + * The audio stream being closed. */ typedef void guac_audio_encoder_end_handler(guac_audio_stream* audio); /** - * Handler which is called when PCM data is written to the audio stream. + * Handler which is called when a new user has joined the Guacamole + * connection associated with the audio stream. + * + * @param audio + * The audio stream associated with the Guacamole connection being + * joined. + * + * @param user + * The user that joined the connection. + */ +typedef void guac_audio_encoder_join_handler(guac_audio_stream* audio, + guac_user* user); + +/** + * Handler which is called when PCM data is written to the audio stream. The + * format of the PCM data is dictated by the properties of the audio stream. + * + * @param audio + * The audio stream to which data is being written. + * + * @param pcm_data + * A buffer containing the raw PCM data to be written. + * + * @param length + * The number of bytes within the buffer that should be written to the + * audio stream. */ typedef void guac_audio_encoder_write_handler(guac_audio_stream* audio, const unsigned char* pcm_data, int length); diff --git a/src/libguac/guacamole/audio.h b/src/libguac/guacamole/audio.h index 6b6696fd..4d28a9cf 100644 --- a/src/libguac/guacamole/audio.h +++ b/src/libguac/guacamole/audio.h @@ -64,6 +64,12 @@ struct guac_audio_encoder { */ guac_audio_encoder_end_handler* end_handler; + /** + * Handler which will be called when a new user joins the Guacamole + * connection associated with an audio stream. + */ + guac_audio_encoder_join_handler* join_handler; + }; struct guac_audio_stream { @@ -108,15 +114,24 @@ struct guac_audio_stream { }; /** - * Allocates a new audio stream which encodes audio data using the given - * encoder. If NULL is specified for the encoder, an appropriate encoder - * will be selected based on the encoders built into libguac and the level - * of client support. The PCM format specified here (via rate, channels, and + * Allocates a new audio stream at the client level which encodes audio data + * using the given encoder. If NULL is specified for the encoder, an + * appropriate encoder will be selected based on the encoders built into + * libguac and the level of support declared by the owner associated with the + * given guac_client. The PCM format specified here (via rate, channels, and * bps) must be the format used for all PCM data provided to the audio stream. * The format may only be changed using guac_audio_stream_reset(). * + * If a new user joins the connection after the audio stream is created, that + * user will not be aware of the existence of the audio stream, and + * guac_audio_stream_add_user() will need to be invoked to recreate the stream + * for the new user. + * * @param client - * The guac_client for which this audio stream is being allocated. + * The guac_client for which this audio stream is being allocated. Only the + * connection owner is used to determine the level of audio support, and it + * is currently assumed that all other joining users on the connection will + * have the same level of audio support. * * @param encoder * The guac_audio_encoder to use when encoding audio, or NULL if libguac @@ -135,7 +150,8 @@ struct guac_audio_stream { * * @return * The newly allocated guac_audio_stream, or NULL if no audio stream could - * be allocated due to lack of client support. + * be allocated due to lack of support on the part of the connecting + * Guacamole client. */ guac_audio_stream* guac_audio_stream_alloc(guac_client* client, guac_audio_encoder* encoder, int rate, int channels, int bps); @@ -168,6 +184,21 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client, void guac_audio_stream_reset(guac_audio_stream* audio, guac_audio_encoder* encoder, int rate, int channels, int bps); +/** + * Notifies the given audio stream that a user has joined the connection. The + * audio stream itself may need to be restarted. and the audio stream will need + * to be created for the new user to ensure they can properly handle future + * data received along the stream. + * + * @param audio + * The guac_audio_stream associated with the Guacamole connection being + * joined. + * + * @param user + * The user that has joined the Guacamole connection. + */ +void guac_audio_stream_add_user(guac_audio_stream* audio, guac_user* user); + /** * Closes and frees the given audio stream. * diff --git a/src/libguac/guacamole/client-constants.h b/src/libguac/guacamole/client-constants.h index dfa45db6..1ae62962 100644 --- a/src/libguac/guacamole/client-constants.h +++ b/src/libguac/guacamole/client-constants.h @@ -30,7 +30,8 @@ */ /** - * The maximum number of inbound streams supported by any one guac_client. + * The maximum number of inbound or outbound streams supported by any one + * guac_client. */ #define GUAC_CLIENT_MAX_STREAMS 64 @@ -40,26 +41,9 @@ #define GUAC_CLIENT_CLOSED_STREAM_INDEX -1 /** - * The maximum number of objects supported by any one guac_client. + * The character prefix which identifies a client ID. */ -#define GUAC_CLIENT_MAX_OBJECTS 64 - -/** - * The index of an object which has not been defined. - */ -#define GUAC_CLIENT_UNDEFINED_OBJECT_INDEX -1 - -/** - * The stream name reserved for the root of a Guacamole protocol object. - */ -#define GUAC_CLIENT_OBJECT_ROOT_NAME "/" - -/** - * The mimetype of a stream containing a map of available stream names to their - * corresponding mimetypes. The root of a Guacamole protocol object is - * guaranteed to have this type. - */ -#define GUAC_CLIENT_STREAM_INDEX_MIMETYPE "application/vnd.glyptodon.guacamole.stream-index+json" +#define GUAC_CLIENT_ID_PREFIX '$' /** * The flag set in the mouse button mask when the left mouse button is down. diff --git a/src/libguac/guacamole/client-fntypes.h b/src/libguac/guacamole/client-fntypes.h index 1a11f35c..0beca23e 100644 --- a/src/libguac/guacamole/client-fntypes.h +++ b/src/libguac/guacamole/client-fntypes.h @@ -34,102 +34,53 @@ #include "object-types.h" #include "protocol-types.h" #include "stream-types.h" +#include "user-types.h" #include -/** - * Handler for server messages (where "server" refers to the server that - * the proxy client is connected to). - */ -typedef int guac_client_handle_messages(guac_client* client); - -/** - * Handler for Guacamole mouse events. - */ -typedef int guac_client_mouse_handler(guac_client* client, int x, int y, int button_mask); - -/** - * Handler for Guacamole key events. - */ -typedef int guac_client_key_handler(guac_client* client, int keysym, int pressed); - -/** - * Handler for Guacamole clipboard events. - */ -typedef int guac_client_clipboard_handler(guac_client* client, guac_stream* stream, - char* mimetype); -/** - * Handler for Guacamole screen size events. - */ -typedef int guac_client_size_handler(guac_client* client, - int width, int height); - -/** - * Handler for Guacamole file transfer events. - */ -typedef int guac_client_file_handler(guac_client* client, guac_stream* stream, - char* mimetype, char* filename); - -/** - * Handler for Guacamole pipe events. - */ -typedef int guac_client_pipe_handler(guac_client* client, guac_stream* stream, - char* mimetype, char* name); - -/** - * Handler for Guacamole stream blob events. - */ -typedef int guac_client_blob_handler(guac_client* client, guac_stream* stream, - void* data, int length); - -/** - * Handler for Guacamole stream ack events. - */ -typedef int guac_client_ack_handler(guac_client* client, guac_stream* stream, - char* error, guac_protocol_status status); - -/** - * Handler for Guacamole stream end events. - */ -typedef int guac_client_end_handler(guac_client* client, guac_stream* stream); - -/** - * Handler for Guacamole object get events. - */ -typedef int guac_client_get_handler(guac_client* client, guac_object* object, - char* name); - -/** - * Handler for Guacamole object put events. - */ -typedef int guac_client_put_handler(guac_client* client, guac_object* object, - guac_stream* stream, char* mimetype, char* name); - -/** - * Handler for Guacamole audio format events. - */ -typedef int guac_client_audio_handler(guac_client* client, char* mimetype); - -/** - * Handler for Guacamole video format events. - */ -typedef int guac_client_video_handler(guac_client* client, char* mimetype); - /** * Handler for freeing up any extra data allocated by the client * implementation. + * + * @param client + * The client whose extra data should be freed (if any). + * + * @return + * Zero if the data was successfully freed, non-zero if an error prevents + * the data from being freed. */ typedef int guac_client_free_handler(guac_client* client); /** - * Handler for logging messages + * Handler for logging messages related to a given guac_client instance. + * + * @param client + * The client related to the message being logged. + * + * @param level + * The log level at which to log the given message. + * + * @param format + * A printf-style format string, defining the message to be logged. + * + * @param args + * The va_list containing the arguments to be used when filling the + * conversion specifiers ("%s", "%i", etc.) within the format string. */ -typedef void guac_client_log_handler(guac_client* client, guac_client_log_level level, const char* format, va_list args); +typedef void guac_client_log_handler(guac_client* client, + guac_client_log_level level, const char* format, va_list args); /** - * Handler which should initialize the given guac_client. + * The entry point of a client plugin which must initialize the given + * guac_client. In practice, this function will be called "guac_client_init". + * + * @param client + * The guac_client that must be initialized. + * + * @return + * Zero on success, non-zero if initialization fails for any reason. */ -typedef int guac_client_init_handler(guac_client* client, int argc, char** argv); +typedef int guac_client_init_handler(guac_client* client); #endif diff --git a/src/libguac/guacamole/client-types.h b/src/libguac/guacamole/client-types.h index ec32b95a..8a0d3e11 100644 --- a/src/libguac/guacamole/client-types.h +++ b/src/libguac/guacamole/client-types.h @@ -87,11 +87,5 @@ typedef enum guac_client_log_level { } guac_client_log_level; -/** - * Information exposed by the remote client during the connection handshake - * which can be used by a client plugin. - */ -typedef struct guac_client_info guac_client_info; - #endif diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index d777adaa..65dcc2e6 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -32,73 +32,34 @@ #include "client-fntypes.h" #include "client-types.h" #include "client-constants.h" -#include "instruction-types.h" #include "layer-types.h" #include "object-types.h" #include "pool-types.h" #include "socket-types.h" #include "stream-types.h" #include "timestamp-types.h" +#include "user-fntypes.h" +#include "user-types.h" #include + +#include #include -struct guac_client_info { - - /** - * The number of pixels the remote client requests for the display width. - * This need not be honored by a client plugin implementation, but if the - * underlying protocol of the client plugin supports dynamic sizing of the - * screen, honoring the display size request is recommended. - */ - int optimal_width; - - /** - * The number of pixels the remote client requests for the display height. - * This need not be honored by a client plugin implementation, but if the - * underlying protocol of the client plugin supports dynamic sizing of the - * screen, honoring the display size request is recommended. - */ - int optimal_height; - - /** - * NULL-terminated array of client-supported audio mimetypes. If the client - * does not support audio at all, this will be NULL. - */ - char** audio_mimetypes; - - /** - * NULL-terminated array of client-supported video mimetypes. If the client - * does not support video at all, this will be NULL. - */ - char** video_mimetypes; - - /** - * NULL-terminated array of client-supported image mimetypes. Though all - * supported image mimetypes will be listed here, it can be safely assumed - * that all clients will support at least "image/png" and "image/jpeg". - */ - char** image_mimetypes; - - /** - * The DPI of the physical remote display if configured for the optimal - * width/height combination described here. This need not be honored by - * a client plugin implementation, but if the underlying protocol of the - * client plugin supports dynamic sizing of the screen, honoring the - * stated resolution of the display size request is recommended. - */ - int optimal_resolution; - -}; - struct guac_client { /** - * The guac_socket structure to be used to communicate with the web-client. - * It is expected that the implementor of any Guacamole proxy client will + * The guac_socket structure to be used to communicate with all connected + * web-clients (users). Unlike the user-level guac_socket, this guac_socket + * will broadcast instructions to all connected users simultaneously. It + * is expected that the implementor of any Guacamole proxy client will * provide their own mechanism of I/O for their protocol. The guac_socket * structure is used only to communicate conveniently with the Guacamole * web-client. + * + * Because this socket broadcasts to all connected users, this socket MUST + * NOT be used within the same thread as a "leave" or "join" handler. Doing + * so results in undefined behavior, including possible segfaults. */ guac_socket* socket; @@ -110,24 +71,6 @@ struct guac_client { */ guac_client_state state; - /** - * The time (in milliseconds) of receipt of the last sync message from - * the client. - */ - guac_timestamp last_received_timestamp; - - /** - * The time (in milliseconds) that the last sync message was sent to the - * client. - */ - guac_timestamp last_sent_timestamp; - - /** - * Information structure containing properties exposed by the remote - * client during the initial handshake process. - */ - guac_client_info info; - /** * Arbitrary reference to proxy client-specific data. Implementors of a * Guacamole proxy client can store any data they want here, which can then @@ -136,197 +79,10 @@ struct guac_client { void* data; /** - * Handler for server messages. If set, this function will be called - * occasionally by the Guacamole proxy to give the client a chance to - * handle messages from whichever server it is connected to. - * - * Example: - * @code - * int handle_messages(guac_client* client); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->handle_messages = handle_messages; - * } - * @endcode + * The time (in milliseconds) that the last sync message was sent to the + * client. */ - guac_client_handle_messages* handle_messages; - - /** - * Handler for mouse events sent by the Gaucamole web-client. - * - * The handler takes the integer mouse X and Y coordinates, as well as - * a button mask containing the bitwise OR of all button values currently - * being pressed. Those values are: - * - * - * - * - * - * - * - * - *
Button Value
Left 1
Middle 2
Right 4
Scrollwheel Up 8
Scrollwheel Down16
- - * Example: - * @code - * int mouse_handler(guac_client* client, int x, int y, int button_mask); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->mouse_handler = mouse_handler; - * } - * @endcode - */ - guac_client_mouse_handler* mouse_handler; - - /** - * Handler for key events sent by the Guacamole web-client. - * - * The handler takes the integer X11 keysym associated with the key - * being pressed/released, and an integer representing whether the key - * is being pressed (1) or released (0). - * - * Example: - * @code - * int key_handler(guac_client* client, int keysym, int pressed); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->key_handler = key_handler; - * } - * @endcode - */ - guac_client_key_handler* key_handler; - - /** - * Handler for clipboard events sent by the Guacamole web-client. This - * handler will be called whenever the web-client sets the data of the - * clipboard. - * - * The handler takes a guac_stream, which contains the stream index and - * will persist through the duration of the transfer, and the mimetype - * of the data being transferred. - * - * Example: - * @code - * int clipboard_handler(guac_client* client, guac_stream* stream, - * char* mimetype); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->clipboard_handler = clipboard_handler; - * } - * @endcode - */ - guac_client_clipboard_handler* clipboard_handler; - - /** - * Handler for size events sent by the Guacamole web-client. - * - * The handler takes an integer width and integer height, representing - * the current visible screen area of the client. - * - * Example: - * @code - * int size_handler(guac_client* client, int width, int height); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->size_handler = size_handler; - * } - * @endcode - */ - guac_client_size_handler* size_handler; - - /** - * Handler for file events sent by the Guacamole web-client. - * - * The handler takes a guac_stream which contains the stream index and - * will persist through the duration of the transfer, the mimetype of - * the file being transferred, and the filename. - * - * Example: - * @code - * int file_handler(guac_client* client, guac_stream* stream, - * char* mimetype, char* filename); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->file_handler = file_handler; - * } - * @endcode - */ - guac_client_file_handler* file_handler; - - /** - * Handler for pipe events sent by the Guacamole web-client. - * - * The handler takes a guac_stream which contains the stream index and - * will persist through the duration of the transfer, the mimetype of - * the data being transferred, and the pipe name. - * - * Example: - * @code - * int pipe_handler(guac_client* client, guac_stream* stream, - * char* mimetype, char* name); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->pipe_handler = pipe_handler; - * } - * @endcode - */ - guac_client_pipe_handler* pipe_handler; - - /** - * Handler for ack events sent by the Guacamole web-client. - * - * The handler takes a guac_stream which contains the stream index and - * will persist through the duration of the transfer, a string containing - * the error or status message, and a status code. - * - * Example: - * @code - * int ack_handler(guac_client* client, guac_stream* stream, - * char* error, guac_protocol_status status); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->ack_handler = ack_handler; - * } - * @endcode - */ - guac_client_ack_handler* ack_handler; - - /** - * Handler for blob events sent by the Guacamole web-client. - * - * The handler takes a guac_stream which contains the stream index and - * will persist through the duration of the transfer, an arbitrary buffer - * containing the blob, and the length of the blob. - * - * Example: - * @code - * int blob_handler(guac_client* client, guac_stream* stream, - * void* data, int length); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->blob_handler = blob_handler; - * } - * @endcode - */ - guac_client_blob_handler* blob_handler; - - /** - * Handler for stream end events sent by the Guacamole web-client. - * - * The handler takes only a guac_stream which contains the stream index. - * This guac_stream will be disposed of immediately after this event is - * finished. - * - * Example: - * @code - * int end_handler(guac_client* client, guac_stream* stream); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->end_handler = end_handler; - * } - * @endcode - */ - guac_client_end_handler* end_handler; + guac_timestamp last_sent_timestamp; /** * Handler for freeing data when the client is being unloaded. @@ -344,7 +100,7 @@ struct guac_client { * @code * int free_handler(guac_client* client); * - * int guac_client_init(guac_client* client, int argc, char** argv) { + * int guac_client_init(guac_client* client) { * client->free_handler = free_handler; * } * @endcode @@ -375,46 +131,6 @@ struct guac_client { */ guac_client_log_handler* log_handler; - /** - * Handler for get events sent by the Guacamole web-client. - * - * The handler takes a guac_object, containing the object index which will - * persist through the duration of the transfer, and the name of the stream - * being requested. It is up to the get handler to create the required body - * stream. - * - * Example: - * @code - * int get_handler(guac_client* client, guac_object* object, - * char* name); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->get_handler = get_handler; - * } - * @endcode - */ - guac_client_get_handler* get_handler; - - /** - * Handler for put events sent by the Guacamole web-client. - * - * The handler takes a guac_object and guac_stream, which each contain their - * respective indices which will persist through the duration of the - * transfer, the mimetype of the data being transferred, and the name of - * the stream within the object being written to. - * - * Example: - * @code - * int put_handler(guac_client* client, guac_object* object, - * guac_stream* stream, char* mimetype, char* name); - * - * int guac_client_init(guac_client* client, int argc, char** argv) { - * client->put_handler = put_handler; - * } - * @endcode - */ - guac_client_put_handler* put_handler; - /** * Pool of buffer indices. Buffers are simply layers with negative indices. * Note that because guac_pool always gives non-negative indices starting @@ -435,25 +151,11 @@ struct guac_client { guac_pool* __stream_pool; /** - * All available output streams (data going to connected client). + * All available client-level output streams (data going to all connected + * users). */ guac_stream* __output_streams; - /** - * All available input streams (data coming from connected client). - */ - guac_stream* __input_streams; - - /** - * Pool of object indices. - */ - guac_pool* __object_pool; - - /** - * All available objects (arbitrary sets of named streams). - */ - guac_object* __objects; - /** * The unique identifier allocated for the connection, which may * be used within the Guacamole protocol to refer to this connection. @@ -463,6 +165,105 @@ struct guac_client { */ char* connection_id; + /** + * Lock which is acquired when the users list is being manipulated, or when + * the users list is being iterated. + */ + pthread_rwlock_t __users_lock; + + /** + * The first user within the list of all connected users, or NULL if no + * users are currently connected. + */ + guac_user* __users; + + /** + * The user that first created this connection. This user will also have + * their "owner" flag set to a non-zero value. If the owner has left the + * connection, this will be NULL. + */ + guac_user* __owner; + + /** + * The number of currently-connected users. This value may include inactive + * users if cleanup of those users has not yet finished. + */ + int connected_users; + + /** + * Handler for join events, called whenever a new user is joining an active + * connection. Note that because users may leave the connection at any + * time, a reference to a guac_user can become invalid at any time and + * should never be maintained outside the scope of a function invoked by + * libguac to which that guac_user was passed (the scope in which the + * guac_user reference is guaranteed to be valid) UNLESS that reference is + * properly invalidated within the leave_handler. + * + * The handler is given a pointer to a newly-allocated guac_user which + * must then be initialized, if needed. + * + * Example: + * @code + * int join_handler(guac_user* user, int argc, char** argv); + * + * int guac_client_init(guac_client* client) { + * client->join_handler = join_handler; + * } + * @endcode + */ + guac_user_join_handler* join_handler; + + /** + * Handler for leave events, called whenever a new user is leaving an + * active connection. + * + * The handler is given a pointer to the leaving guac_user whose custom + * data and associated resources must now be freed, if any. + * + * Example: + * @code + * int leave_handler(guac_user* user); + * + * int guac_client_init(guac_client* client) { + * client->leave_handler = leave_handler; + * } + * @endcode + */ + guac_user_leave_handler* leave_handler; + + /** + * NULL-terminated array of all arguments accepted by this client , in + * order. New users will specify these arguments when they join the + * connection, and the values of those arguments will be made available to + * the function initializing newly-joined users. + * + * The guac_client_init entry point is expected to initialize this, if + * arguments are expected. + * + * Example: + * @code + * const char* __my_args[] = { + * "hostname", + * "port", + * "username", + * "password", + * NULL + * }; + * + * int guac_client_init(guac_client* client) { + * client->args = __my_args; + * } + * @endcode + */ + const char** args; + + /** + * Handle to the dlopen()'d plugin, which should be given to dlclose() when + * this client is freed. This is only assigned if guac_client_load_plugin() + * is used. + */ + void* __plugin_handle; + }; /** @@ -480,25 +281,6 @@ guac_client* guac_client_alloc(); */ void guac_client_free(guac_client* client); -/** - * Call the appropriate handler defined by the given client for the given - * instruction. A comparison is made between the instruction opcode and the - * initial handler lookup table defined in client-handlers.c. The intial - * handlers will in turn call the client's handler (if defined). - * - * @param client - * The proxy client whose handlers should be called. - * - * @param instruction - * The instruction to pass to the proxy client via the appropriate handler. - * - * @return - * Non-negative if the instruction was handled successfully, or negative - * if an error occurred. - */ -int guac_client_handle_instruction(guac_client* client, - guac_instruction* instruction); - /** * Writes a message in the log used by the given client. The logger used will * normally be defined by guacd (or whichever program loads the proxy client) @@ -604,8 +386,11 @@ void guac_client_free_layer(guac_client* client, guac_layer* layer); * Allocates a new stream. An arbitrary index is automatically assigned * if no previously-allocated stream is available for use. * - * @param client The proxy client to allocate the layer buffer for. - * @return The next available stream, or a newly allocated stream. + * @param client + * The client to allocate the stream for. + * + * @return + * The next available stream, or a newly allocated stream. */ guac_stream* guac_client_alloc_stream(guac_client* client); @@ -613,34 +398,134 @@ guac_stream* guac_client_alloc_stream(guac_client* client); * Returns the given stream to the pool of available streams, such that it * can be reused by any subsequent call to guac_client_alloc_stream(). * - * @param client The proxy client to return the buffer to. - * @param stream The stream to return to the pool of available stream. + * @param client + * The client to return the stream to. + * + * @param stream + * The stream to return to the pool of available stream. */ void guac_client_free_stream(guac_client* client, guac_stream* stream); /** - * Allocates a new object. An arbitrary index is automatically assigned - * if no previously-allocated object is available for use. + * Adds the given user to the internal list of connected users. Future writes + * to the broadcast socket stored within guac_client will also write to this + * user. The join handler of this guac_client will be called. * - * @param client - * The proxy client to allocate the object for. - * - * @return - * The next available object, or a newly allocated object. + * @param client The proxy client to add the user to. + * @param user The user to add. + * @param argc The number of arguments to pass to the new user. + * @param argv An array of strings containing the argument values being passed. + * @return Zero if the user was added successfully, non-zero if the user could + * not join the connection. */ -guac_object* guac_client_alloc_object(guac_client* client); +int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** argv); /** - * Returns the given object to the pool of available objects, such that it - * can be reused by any subsequent call to guac_client_alloc_object(). + * Removes the given user, removing the user from the internally-tracked list + * of connected users, and calling any appropriate leave handler. + * + * @param client The proxy client to return the buffer to. + * @param user The user to remove. + */ +void guac_client_remove_user(guac_client* client, guac_user* user); + +/** + * Calls the given function on all currently-connected users of the given + * client. The function will be given a reference to a guac_user and the + * specified arbitrary data. The value returned by the callback will be + * ignored. + * + * This function is reentrant, but the user list MUST NOT be manipulated + * within the same thread as a callback to this function. Though the callback + * MAY invoke guac_client_foreach_user(), doing so should not be necessary, and + * may indicate poor design choices. + * + * Because this function loops through all connected users, this function MUST + * NOT be invoked within the same thread as a "leave" or "join" handler. Doing + * so results in undefined behavior, including possible segfaults. * * @param client - * The proxy client to return the object to. + * The client whose users should be iterated. * - * @param object - * The object to return to the pool of available object. + * @param callback + * The function to call for each user. + * + * @param data + * Arbitrary data to pass to the callback each time it is invoked. */ -void guac_client_free_object(guac_client* client, guac_object* object); +void guac_client_foreach_user(guac_client* client, + guac_user_callback* callback, void* data); + +/** + * Retrieves the connected user that is marked as the owner. The owner of a + * connection is the user that established the initial connection that created + * the connection (the first user to connect and join). + * + * Calls the given function on with the currently-connected user that is marked + * as the owner. The owner of a connection is the user that established the + * initial connection that created the connection (the first user to connect + * and join). The function will be given a reference to a guac_user and the + * specified arbitrary data. If the owner has since left the connection, the + * function will instead be invoked with NULL as the guac_user. The value + * returned by the callback will be returned by this function. + * + * This function is reentrant, but the user list MUST NOT be manipulated + * within the same thread as a callback to this function. + * + * Because this function depends on a consistent list of connected users, this + * function MUST NOT be invoked within the same thread as a "leave" or "join" + * handler. Doing so results in undefined behavior, including possible + * segfaults. + * + * @param client + * The client to retrieve the owner from. + * + * @return + * The value returned by the callback. + */ +void* guac_client_for_owner(guac_client* client, guac_user_callback* callback, + void* data); + +/** + * Marks the end of the current frame by sending a "sync" instruction to + * all connected users. This instruction will contain the current timestamp. + * The last_sent_timestamp member of guac_client will be updated accordingly. + * + * If an error occurs sending the instruction, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param client The guac_client which has finished a frame. + * @return Zero on success, non-zero on error. + */ +int guac_client_end_frame(guac_client* client); + +/** + * Initializes the given guac_client using the initialization routine provided + * by the plugin corresponding to the named protocol. This will automatically + * invoke guac_client_init within the plugin for the given protocol. + * + * Note that the connection will likely not be established until the first + * user (the "owner") is added to the client. + * + * @param client The guac_client to initialize. + * @param protocol The name of the protocol to use. + * @return Zero if initialization was successful, non-zero otherwise. + */ +int guac_client_load_plugin(guac_client* client, const char* protocol); + +/** + * Calculates and returns the approximate processing lag experienced by the + * pool of users. The processing lag is the difference in time between server + * and client due purely to data processing and excluding network delays. + * + * @param client + * The guac_client to calculate the processing lag of. + * + * @return + * The approximate processing lag of the pool of users associated with the + * given guac_client, in milliseconds. + */ +int guac_client_get_processing_lag(guac_client* client); /** * Streams the image data of the given surface over an image stream ("img" @@ -648,7 +533,7 @@ void guac_client_free_object(guac_client* client, guac_object* object); * allocated and freed. * * @param client - * The Guacamole client from which the image stream should be allocated. + * The Guacamole client for which the image stream should be allocated. * * @param socket * The socket over which instructions associated with the image stream @@ -681,7 +566,7 @@ void guac_client_stream_png(guac_client* client, guac_socket* socket, * will be automatically allocated and freed. * * @param client - * The Guacamole client from which the image stream should be allocated. + * The Guacamole client for which the image stream should be allocated. * * @param socket * The socket over which instructions associated with the image stream @@ -705,8 +590,9 @@ void guac_client_stream_png(guac_client* client, guac_socket* socket, * A Cairo surface containing the image data to be streamed. * * @param quality - * The JPEG image quality, which must be an integer value between 0 and - * 100 inclusive. + * The JPEG image quality, which must be an integer value between 0 and 100 + * inclusive. Larger values indicate improving quality at the expense of + * larger file size. */ void guac_client_stream_jpeg(guac_client* client, guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int x, int y, @@ -720,7 +606,7 @@ void guac_client_stream_jpeg(guac_client* client, guac_socket* socket, * guac_client_supports_webp() prior to calling this function. * * @param client - * The Guacamole client from which the image stream should be allocated. + * The Guacamole client for whom the image stream should be allocated. * * @param socket * The socket over which instructions associated with the image stream @@ -758,15 +644,15 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket, cairo_surface_t* surface, int quality, int lossless); /** - * Returns whether the given client supports WebP. If the client does not - * support WebP, or the server cannot encode WebP images, zero is returned. + * Returns whether all users of the given client support WebP. If any user does + * not support WebP, or the server cannot encode WebP images, zero is returned. * * @param client - * The Guacamole client to check for WebP support. + * The Guacamole client whose users should be checked for WebP support. * * @return - * Non-zero if the given client claims to support WebP and the server has - * been built with WebP support, zero otherwise. + * Non-zero if the all users of the given client claim to support WebP and + * the server has been built with WebP support, zero otherwise. */ int guac_client_supports_webp(guac_client* client); diff --git a/src/libguac/guacamole/instruction.h b/src/libguac/guacamole/instruction.h deleted file mode 100644 index 9444ad93..00000000 --- a/src/libguac/guacamole/instruction.h +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - - -#ifndef _GUAC_INSTRUCTION_H -#define _GUAC_INSTRUCTION_H - -/** - * Provides functions and structures for reading, writing, and manipulating - * Guacamole instructions. - * - * @file instruction.h - */ - -#include "instruction-types.h" -#include "instruction-constants.h" -#include "socket-types.h" - -struct guac_instruction { - - /** - * The opcode of the instruction. - */ - char* opcode; - - /** - * The number of arguments passed to this instruction. - */ - int argc; - - /** - * Array of all arguments passed to this instruction. - */ - char** argv; - - /** - * The parse state of the instruction. - */ - guac_instruction_parse_state state; - - /** - * The length of the current element, if known. - */ - int __element_length; - - /** - * The number of elements currently parsed. - */ - int __elementc; - - /** - * All currently parsed elements. - */ - char* __elementv[GUAC_INSTRUCTION_MAX_ELEMENTS]; - -}; - -/** - * Allocates a new instruction. Each instruction contains within itself the - * necessary facilities to parse instruction data. - * - * @return The newly allocated instruction, or NULL if an error occurs during - * allocation, in which case guac_error will be set appropriately. - */ -guac_instruction* guac_instruction_alloc(); - -/** - * Resets the parse state and contents of the given instruction, such that the - * memory of that instruction can be reused for another parse cycle. - * - * @param instruction The instruction to reset. - */ -void guac_instruction_reset(guac_instruction* instruction); - -/** - * Appends data from the given buffer to the given instruction. The data will - * be appended, if possible, to this instruction as a reference and thus the - * buffer must remain valid throughout the life of the instruction. This - * function may modify the contents of the buffer when those contents are - * part of an element within the instruction being read. - * - * @param instruction The instruction to append data to. - * @param buffer A buffer containing data that should be appended to this - * instruction. - * @param length The number of bytes available for appending within the buffer. - * @return The number of bytes appended to this instruction, which may be - * zero if more data is needed. - */ -int guac_instruction_append(guac_instruction* instruction, - void* buffer, int length); - -/** - * Frees all memory allocated to the given instruction. - * - * @param instruction The instruction to free. - */ -void guac_instruction_free(guac_instruction* instruction); - -/** - * Returns whether new instruction data is available on the given guac_socket - * connection for parsing. - * - * @param socket The guac_socket connection to use. - * @param usec_timeout The maximum number of microseconds to wait before - * giving up. - * @return A positive value if data is available, negative on error, or - * zero if no data is currently available. - */ -int guac_instruction_waiting(guac_socket* socket, int usec_timeout); - -/** - * Reads a single instruction from the given guac_socket connection. - * - * If an error occurs reading the instruction, NULL is returned, - * and guac_error is set appropriately. - * - * @param socket The guac_socket connection to use. - * @param usec_timeout The maximum number of microseconds to wait before - * giving up. - * @return A new instruction if data was successfully read, NULL on - * error or if the instruction could not be read completely - * because the timeout elapsed, in which case guac_error will be - * set to GUAC_STATUS_INPUT_TIMEOUT and subsequent calls to - * guac_protocol_read_instruction() will return the parsed instruction - * once enough data is available. - */ -guac_instruction* guac_instruction_read(guac_socket* socket, int usec_timeout); - -/** - * Reads a single instruction with the given opcode from the given guac_socket - * connection. - * - * If an error occurs reading the instruction, NULL is returned, - * and guac_error is set appropriately. - * - * If the instruction read is not the expected instruction, NULL is returned, - * and guac_error is set to GUAC_STATUS_BAD_STATE. - * - * @param socket The guac_socket connection to use. - * @param usec_timeout The maximum number of microseconds to wait before - * giving up. - * @param opcode The opcode of the instruction to read. - * @return A new instruction if an instruction with the given opcode was read, - * NULL otherwise. If an instruction was read, but the instruction had - * a different opcode, NULL is returned and guac_error is set to - * GUAC_STATUS_BAD_STATE. - */ -guac_instruction* guac_instruction_expect(guac_socket* socket, - int usec_timeout, const char* opcode); - -#endif - diff --git a/src/libguac/guacamole/object.h b/src/libguac/guacamole/object.h index 01be132a..2ce0a1a3 100644 --- a/src/libguac/guacamole/object.h +++ b/src/libguac/guacamole/object.h @@ -29,8 +29,8 @@ * @file object.h */ -#include "client-fntypes.h" #include "object-types.h" +#include "user-fntypes.h" struct guac_object { @@ -54,18 +54,18 @@ struct guac_object { * * Example: * @code - * int get_handler(guac_client* client, guac_object* object, + * int get_handler(guac_user* user, guac_object* object, * char* name); * - * int some_function(guac_client* client) { + * int some_function(guac_user* user) { * - * guac_object* object = guac_client_alloc_object(client); + * guac_object* object = guac_user_alloc_object(user); * object->get_handler = get_handler; * * } * @endcode */ - guac_client_get_handler* get_handler; + guac_user_get_handler* get_handler; /** * Handler for put events sent by the Guacamole web-client. @@ -77,18 +77,18 @@ struct guac_object { * * Example: * @code - * int put_handler(guac_client* client, guac_object* object, + * int put_handler(guac_user* user, guac_object* object, * guac_stream* stream, char* mimetype, char* name); * - * int some_function(guac_client* client) { + * int some_function(guac_user* user) { * - * guac_object* object = guac_client_alloc_object(client); + * guac_object* object = guac_user_alloc_object(user); * object->put_handler = put_handler; * * } * @endcode */ - guac_client_put_handler* put_handler; + guac_user_put_handler* put_handler; }; diff --git a/src/libguac/guacamole/instruction-constants.h b/src/libguac/guacamole/parser-constants.h similarity index 90% rename from src/libguac/guacamole/instruction-constants.h rename to src/libguac/guacamole/parser-constants.h index ac295dcf..3995dd00 100644 --- a/src/libguac/guacamole/instruction-constants.h +++ b/src/libguac/guacamole/parser-constants.h @@ -20,13 +20,13 @@ * THE SOFTWARE. */ -#ifndef _GUAC_INSTRUCTION_CONSTANTS_H -#define _GUAC_INSTRUCTION_CONSTANTS_H +#ifndef _GUAC_PARSER_CONSTANTS_H +#define _GUAC_PARSER_CONSTANTS_H /** - * Constants related to Guacamole instructions. + * Constants related to the Guacamole protocol parser. * - * @file instruction-constants.h + * @file parser-constants.h */ /** diff --git a/src/libguac/guacamole/instruction-types.h b/src/libguac/guacamole/parser-types.h similarity index 77% rename from src/libguac/guacamole/instruction-types.h rename to src/libguac/guacamole/parser-types.h index fd6cc693..5691abe8 100644 --- a/src/libguac/guacamole/instruction-types.h +++ b/src/libguac/guacamole/parser-types.h @@ -21,48 +21,49 @@ */ -#ifndef _GUAC_INSTRUCTION_TYPES_H -#define _GUAC_INSTRUCTION_TYPES_H +#ifndef _GUAC_PARSER_TYPES_H +#define _GUAC_PARSER_TYPES_H /** - * Type definitions related to Guacamole instructions. + * Type definitions related to parsing the Guacamole protocol. * - * @file instruction-types.h + * @file parser-types.h */ /** * All possible states of the instruction parser. */ -typedef enum guac_instruction_parse_state { +typedef enum guac_parse_state { /** * The parser is currently waiting for data to complete the length prefix * of the current element of the instruction. */ - GUAC_INSTRUCTION_PARSE_LENGTH, + GUAC_PARSE_LENGTH, /** * The parser has finished reading the length prefix and is currently * waiting for data to complete the content of the instruction. */ - GUAC_INSTRUCTION_PARSE_CONTENT, + GUAC_PARSE_CONTENT, /** * The instruction has been fully parsed. */ - GUAC_INSTRUCTION_PARSE_COMPLETE, + GUAC_PARSE_COMPLETE, /** * The instruction cannot be parsed because of a protocol error. */ - GUAC_INSTRUCTION_PARSE_ERROR + GUAC_PARSE_ERROR -} guac_instruction_parse_state; +} guac_parse_state; /** - * Represents a single instruction within the Guacamole protocol. + * A Guacamole protocol parser, which reads individual instructions, filling + * its own internal structure with the most recently read instruction data. */ -typedef struct guac_instruction guac_instruction; +typedef struct guac_parser guac_parser; #endif diff --git a/src/libguac/guacamole/parser.h b/src/libguac/guacamole/parser.h new file mode 100644 index 00000000..9681ac62 --- /dev/null +++ b/src/libguac/guacamole/parser.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef _GUAC_PARSER_H +#define _GUAC_PARSER_H + +/** + * Provides functions and structures for parsing the Guacamole protocol. + * + * @file parser.h + */ + +#include "parser-types.h" +#include "parser-constants.h" +#include "socket-types.h" + +struct guac_parser { + + /** + * The opcode of the instruction. + */ + char* opcode; + + /** + * The number of arguments passed to this instruction. + */ + int argc; + + /** + * Array of all arguments passed to this instruction. + */ + char** argv; + + /** + * The parse state of the instruction. + */ + guac_parse_state state; + + /** + * The length of the current element, if known. + */ + int __element_length; + + /** + * The number of elements currently parsed. + */ + int __elementc; + + /** + * All currently parsed elements. + */ + char* __elementv[GUAC_INSTRUCTION_MAX_ELEMENTS]; + + /** + * Pointer to the first character of the current in-progress instruction + * within the buffer. + */ + char* __instructionbuf_unparsed_start; + + /** + * Pointer to the first unused section of the instruction buffer. + */ + char* __instructionbuf_unparsed_end; + + /** + * The instruction buffer. This is essentially the input buffer, + * provided as a convenience to be used to buffer instructions until + * those instructions are complete and ready to be parsed. + */ + char __instructionbuf[32768]; + +}; + +/** + * Allocates a new parser. + * + * @return The newly allocated parser, or NULL if an error occurs during + * allocation, in which case guac_error will be set appropriately. + */ +guac_parser* guac_parser_alloc(); + +/** + * Appends data from the given buffer to the given parser. The data will be + * appended, if possible, to the in-progress instruction as a reference and + * thus the buffer must remain valid throughout the life of the current + * instruction. This function may modify the contents of the buffer when those + * contents are part of an element within the instruction being read. + * + * @param parser The parser to append data to. + * @param buffer A buffer containing data that should be appended to this + * parser. + * @param length The number of bytes available for appending within the buffer. + * @return The number of bytes appended to this parser, which may be + * zero if more data is needed. + */ +int guac_parser_append(guac_parser* parser, void* buffer, int length); + +/** + * Returns the number of unparsed bytes stored in the given parser's internal + * buffers. + * + * @param parser The parser to return the length of. + * @return The number of unparsed bytes stored in the given parser. + */ +int guac_parser_length(guac_parser* parser); + +/** + * Removes up to length bytes from internal buffer of unparsed bytes, storing + * them in the given buffer. + * + * @param parser The parser to remove unparsed bytes from. + * @param buffer The buffer to store the unparsed bytes within. + * @param length The length of the given buffer. + * @return The number of bytes stored in the given buffer. + */ +int guac_parser_shift(guac_parser* parser, void* buffer, int length); + +/** + * Frees all memory allocated to the given parser. + * + * @param parser The parser to free. + */ +void guac_parser_free(guac_parser* parser); + +/** + * Reads a single instruction from the given guac_socket connection. This + * may result in additional data being read from the guac_socket, stored + * internally within a buffer for future parsing. Future calls to + * guac_parser_read() will read from the interal buffer before reading + * from the guac_socket. Data from the internal buffer can be removed + * and used elsewhere through guac_parser_shift(). + * + * If an error occurs reading the instruction, non-zero is returned, + * and guac_error is set appropriately. + * + * @param parser The guac_parser to read instruction data from. + * @param socket The guac_socket connection to use. + * @param usec_timeout The maximum number of microseconds to wait before + * giving up. + * @return Zero if an instruction was read within the time allowed, or + * non-zero if no instruction could be read. If the instruction + * could not be read completely because the timeout elapsed, in + * which case guac_error will be set to GUAC_STATUS_INPUT_TIMEOUT + * and additional calls to guac_parser_read() will be required. + */ +int guac_parser_read(guac_parser* parser, guac_socket* socket, int usec_timeout); + +/** + * Reads a single instruction from the given guac_socket. This operates + * identically to guac_parser_read(), except that an error is returned if + * the expected opcode is not received. + * + * If an error occurs reading the instruction, NULL is returned, + * and guac_error is set appropriately. + * + * If the instruction read is not the expected instruction, NULL is returned, + * and guac_error is set to GUAC_STATUS_BAD_STATE. + * + * @param parser The guac_parser to read instruction data from. + * @param socket The guac_socket connection to use. + * @param usec_timeout The maximum number of microseconds to wait before + * giving up. + * @param opcode The opcode of the instruction to read. + * @return Zero if an instruction with the given opcode was read, non-zero + * otherwise. If an instruction was read, but the instruction had a + * different opcode, non-zero is returned and guac_error is set to + * GUAC_STATUS_BAD_STATE. + */ +int guac_parser_expect(guac_parser* parser, guac_socket* socket, int usec_timeout, const char* opcode); + +#endif + diff --git a/src/libguac/guacamole/plugin.h b/src/libguac/guacamole/plugin.h index 52a7ba73..8ce05c8a 100644 --- a/src/libguac/guacamole/plugin.h +++ b/src/libguac/guacamole/plugin.h @@ -23,9 +23,7 @@ #ifndef _GUAC_PLUGIN_H #define _GUAC_PLUGIN_H -#include "client-types.h" #include "plugin-constants.h" -#include "plugin-types.h" /** * Provides functions and structures required for handling a client plugin. @@ -33,60 +31,4 @@ * @file plugin.h */ -struct guac_client_plugin { - - /** - * Reference to dlopen'd client plugin. - */ - void* __client_plugin_handle; - - /** - * Reference to the init handler of this client plugin. This - * function will be called when the client plugin is started. - */ - guac_client_init_handler* init_handler; - - /** - * NULL-terminated array of all arguments accepted by this client - * plugin, in order. The values of these arguments will be passed - * to the init_handler if the client plugin is started. - */ - const char** args; - -}; - -/** - * Open the plugin which provides support for the given protocol, if it - * exists. - * - * @param protocol The name of the protocol to retrieve the client plugin - * for. - * @return The client plugin supporting the given protocol, or NULL if - * an error occurs or no such plugin exists. - */ -guac_client_plugin* guac_client_plugin_open(const char* protocol); - -/** - * Close the given plugin, releasing all associated resources. This function - * must be called after use of a client plugin is finished. - * - * @param plugin The client plugin to close. - * @return Zero on success, non-zero if an error occurred while releasing - * the resources associated with the plugin. - */ -int guac_client_plugin_close(guac_client_plugin* plugin); - -/** - * Initializes the given guac_client using the initialization routine provided - * by the given guac_client_plugin. - * - * @param plugin The client plugin to use to initialize the new client. - * @param client The guac_client to initialize. - * @param argc The number of arguments being passed to the client. - * @param argv All arguments to be passed to the client. - * @return Zero if initialization was successful, non-zero otherwise. - */ -int guac_client_plugin_init_client(guac_client_plugin* plugin, - guac_client* client, int argc, char** argv); - #endif diff --git a/src/libguac/guacamole/pool.h b/src/libguac/guacamole/pool.h index 5ab99eae..e06b761f 100644 --- a/src/libguac/guacamole/pool.h +++ b/src/libguac/guacamole/pool.h @@ -32,6 +32,8 @@ #include "pool-types.h" +#include + struct guac_pool { /** @@ -62,6 +64,11 @@ struct guac_pool { */ guac_pool_int* __tail; + /** + * Lock which is acquired when the pool is being modified or accessed. + */ + pthread_mutex_t __lock; + }; struct guac_pool_int { @@ -99,21 +106,29 @@ void guac_pool_free(guac_pool* pool); /** * Returns the next available integer from the given guac_pool. All integers * returned are non-negative, and are returned in sequences, starting from 0. + * This operation is threadsafe. * - * @param pool The guac_pool to retrieve an integer from. - * @return The next available integer, which may be either an integer not yet - * returned by a call to guac_pool_next_int, or an integer which was - * previosly returned, but has since been freed. + * @param pool + * The guac_pool to retrieve an integer from. + * + * @return + * The next available integer, which may be either an integer not yet + * returned by a call to guac_pool_next_int, or an integer which was + * previously returned, but has since been freed. */ int guac_pool_next_int(guac_pool* pool); /** * Frees the given integer back into the given guac_pool. The integer given - * will be available for future calls to guac_pool_next_int. + * will be available for future calls to guac_pool_next_int. This operation is + * threadsafe. * - * @param pool The guac_pool to free the given integer into. - * @param value The integer which should be returned to the given pool, such - * that it can be received by a future call to guac_pool_next_int. + * @param pool + * The guac_pool to free the given integer into. + * + * @param value + * The integer which should be returned to the given pool, such that it can + * be received by a future call to guac_pool_next_int. */ void guac_pool_free_int(guac_pool* pool, int value); diff --git a/src/libguac/guacamole/socket-fntypes.h b/src/libguac/guacamole/socket-fntypes.h index 8862c160..cadc80c1 100644 --- a/src/libguac/guacamole/socket-fntypes.h +++ b/src/libguac/guacamole/socket-fntypes.h @@ -49,11 +49,11 @@ typedef ssize_t guac_socket_read_handler(guac_socket* socket, /** * Generic write handler for socket write operations, modeled after the standard * POSIX write() function. When set within a guac_socket, a handler of this type - * will be called when data needs to be write into the socket. + * will be called when data needs to be written to the socket. * * @param socket The guac_socket being written to. * @param buf The arbitrary buffer containing data to be written. - * @param count The maximum number of bytes to write from the buffer. + * @param count The maximum number of bytes to written to the buffer. * @return The number of bytes written, or -1 if an error occurs. */ typedef ssize_t guac_socket_write_handler(guac_socket* socket, @@ -72,6 +72,39 @@ typedef ssize_t guac_socket_write_handler(guac_socket* socket, */ typedef int guac_socket_select_handler(guac_socket* socket, int usec_timeout); +/** + * Generic flush handler for socket flush operations. This function is not + * modeled after any POSIX function. When set within a guac_socket, a handler + * of this type will be called when guac_socket_flush() is called. + * + * @param socket + * The guac_socket being flushed. + * + * @return + * Zero on success, or non-zero if an error occurs during flush. + */ +typedef ssize_t guac_socket_flush_handler(guac_socket* socket); + +/** + * When set within a guac_socket, a handler of this type will be called + * whenever exclusive access to the guac_socket is required, such as when + * guac_socket_instruction_begin() is called. + * + * @param socket + * The guac_socket to which exclusive access is required. + */ +typedef void guac_socket_lock_handler(guac_socket* socket); + +/** + * When set within a guac_socket, a handler of this type will be called + * whenever exclusive access to the guac_socket is no longer required, such as + * when guac_socket_instruction_end() is called. + * + * @param socket + * The guac_socket to which exclusive access is no longer required. + */ +typedef void guac_socket_unlock_handler(guac_socket* socket); + /** * Generic handler for the closing of a socket, modeled after the standard * POSIX close() function. When set within a guac_socket, a handler of this type diff --git a/src/libguac/guacamole/socket.h b/src/libguac/guacamole/socket.h index 50bd9344..27d0be5b 100644 --- a/src/libguac/guacamole/socket.h +++ b/src/libguac/guacamole/socket.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -52,13 +52,28 @@ struct guac_socket { /** * Handler which will be called whenever data is written to this socket. - * Note that because guac_socket automatically buffers written data, this - * handler might only get called when the socket is flushed. */ guac_socket_write_handler* write_handler; /** - * Handler which will be called whenever guac_socket_select is invoked + * Handler which will be called whenever this socket needs to be flushed. + */ + guac_socket_flush_handler* flush_handler; + + /** + * Handler which will be called whenever a socket needs to be acquired for + * exclusive access, such as when an instruction is about to be written. + */ + guac_socket_lock_handler* lock_handler; + + /** + * Handler which will be called whenever exclusive access to a socket is + * being released, such as when an instruction has finished being written. + */ + guac_socket_unlock_handler* unlock_handler; + + /** + * Handler which will be called whenever guac_socket_select() is invoked * on this socket. */ guac_socket_select_handler* select_handler; @@ -90,52 +105,6 @@ struct guac_socket { */ int __ready_buf[3]; - /** - * The number of bytes currently in the main write buffer. - */ - int __written; - - /** - * The main write buffer. Bytes written go here before being flushed - * to the open file descriptor. - */ - char __out_buf[GUAC_SOCKET_OUTPUT_BUFFER_SIZE]; - - /** - * Pointer to the first character of the current in-progress instruction - * within the buffer. - */ - char* __instructionbuf_unparsed_start; - - /** - * Pointer to the first unused section of the instruction buffer. - */ - char* __instructionbuf_unparsed_end; - - /** - * The instruction buffer. This is essentially the input buffer, - * provided as a convenience to be used to buffer instructions until - * those instructions are complete and ready to be parsed. - */ - char __instructionbuf[32768]; - - /** - * Whether instructions should be guaranteed atomic across threads using - * locks. By default, thread safety is disabled on sockets. - */ - int __threadsafe_instructions; - - /** - * Lock which is acquired when an instruction is being written, and - * released when the instruction is finished being written. - */ - pthread_mutex_t __instruction_write_lock; - - /** - * Lock which is acquired when the buffer is being modified or flushed. - */ - pthread_mutex_t __buffer_lock; - /** * Whether automatic keep-alive is enabled. */ @@ -164,66 +133,36 @@ guac_socket* guac_socket_alloc(); */ void guac_socket_free(guac_socket* socket); -/** - * Declares that the given socket must behave in a threadsafe way. Calling - * this function on a socket guarantees that the socket will send instructions - * atomically. Without automatic threadsafe sockets, multiple threads writing - * to the same socket must ensure that instructions will not potentially - * overlap. - * - * @param socket The guac_socket to declare as threadsafe. - */ -void guac_socket_require_threadsafe(guac_socket* socket); - /** * Declares that the given socket must automatically send a keep-alive ping * to ensure neither side of the socket times out while the socket is open. - * This ping will take the form of a "nop" instruction. Enabling keep-alive - * automatically enables threadsafety. + * This ping will take the form of a "nop" instruction. * - * @param socket The guac_socket to declare as requiring an automatic - * keep-alive ping. + * @param socket + * The guac_socket to declare as requiring an automatic keep-alive ping. */ void guac_socket_require_keep_alive(guac_socket* socket); /** - * Marks the beginning of a Guacamole protocol instruction. If threadsafety - * is enabled on the socket, other instructions will be blocked from sending - * until this instruction is complete. + * Marks the beginning of a Guacamole protocol instruction. * - * @param socket The guac_socket beginning an instruction. + * @param socket + * The guac_socket beginning an instruction. */ void guac_socket_instruction_begin(guac_socket* socket); /** - * Marks the end of a Guacamole protocol instruction. If threadsafety - * is enabled on the socket, other instructions will be allowed to send. + * Marks the end of a Guacamole protocol instruction. * - * @param socket The guac_socket ending an instruction. + * @param socket + * The guac_socket ending an instruction. */ void guac_socket_instruction_end(guac_socket* socket); -/** - * Marks the beginning of a socket's buffer modification. If threadsafety is - * enabled on the socket, other functions which modify the buffer will be - * blocked until this modification is complete. - * - * @param socket The guac_socket whose buffer is being updated. - */ -void guac_socket_update_buffer_begin(guac_socket* socket); - -/** - * Marks the end of a socket's buffer modification. If threadsafety is enabled - * on the socket, other functions which modify the buffer will now be allowed - * to continue. - * - * @param socket The guac_socket whose buffer is done being updated. - */ -void guac_socket_update_buffer_end(guac_socket* socket); - /** * Allocates and initializes a new guac_socket object with the given open - * file descriptor. + * file descriptor. The file descriptor will be automatically closed when + * the allocated guac_socket is freed. * * If an error occurs while allocating the guac_socket object, NULL is returned, * and guac_error is set appropriately. @@ -237,7 +176,8 @@ guac_socket* guac_socket_open(int fd); /** * Allocates and initializes a new guac_socket which writes all data via - * nest instructions to the given existing, open guac_socket. + * nest instructions to the given existing, open guac_socket. Freeing the + * returned guac_socket has no effect on the underlying, nested guac_socket. * * If an error occurs while allocating the guac_socket object, NULL is returned, * and guac_error is set appropriately. @@ -268,9 +208,7 @@ ssize_t guac_socket_write_int(guac_socket* socket, int64_t i); /** * Writes the given string to the given guac_socket object. The data * written may be buffered until the buffer is flushed automatically or - * manually. Note that if the string can contain characters used - * internally by the Guacamole protocol (commas, semicolons, or - * backslashes) it will need to be escaped. + * manually. * * If an error occurs while writing, a non-zero value is returned, and * guac_error is set appropriately. @@ -284,9 +222,9 @@ ssize_t guac_socket_write_string(guac_socket* socket, const char* str); /** * Writes the given binary data to the given guac_socket object as base64- * encoded data. The data written may be buffered until the buffer is flushed - * automatically or manually. Beware that because base64 data is buffered + * automatically or manually. Beware that, because base64 data is buffered * on top of the write buffer already used, a call to guac_socket_flush_base64() - * must be made before non-base64 writes (or writes of an independent block of + * MUST be made before non-base64 writes (or writes of an independent block of * base64 data) can be made. * * If an error occurs while writing, a non-zero value is returned, and @@ -300,8 +238,8 @@ ssize_t guac_socket_write_string(guac_socket* socket, const char* str); ssize_t guac_socket_write_base64(guac_socket* socket, const void* buf, size_t count); /** - * Writes the given data to the specified socket. The data written is not - * buffered, and will be sent immediately. + * Writes the given data to the specified socket. The data written may be + * buffered until the buffer is flushed automatically or manually. * * If an error occurs while writing, a non-zero value is returned, and * guac_error is set appropriately. diff --git a/src/libguac/guacamole/stream.h b/src/libguac/guacamole/stream.h index 01102e17..ef73277e 100644 --- a/src/libguac/guacamole/stream.h +++ b/src/libguac/guacamole/stream.h @@ -29,7 +29,7 @@ * @file stream.h */ -#include "client-fntypes.h" +#include "user-fntypes.h" #include "stream-types.h" struct guac_stream { @@ -53,21 +53,21 @@ struct guac_stream { * * Example: * @code - * int ack_handler(guac_client* client, guac_stream* stream, + * int ack_handler(guac_user* user, guac_stream* stream, * char* error, guac_protocol_status status); * - * int some_function(guac_client* client) { + * int some_function(guac_user* user) { * - * guac_stream* stream = guac_client_alloc_stream(client); + * guac_stream* stream = guac_user_alloc_stream(user); * stream->ack_handler = ack_handler; * - * guac_protocol_send_clipboard(client->socket, + * guac_protocol_send_clipboard(user->socket, * stream, "text/plain"); * * } * @endcode */ - guac_client_ack_handler* ack_handler; + guac_user_ack_handler* ack_handler; /** * Handler for blob events sent by the Guacamole web-client. @@ -78,16 +78,16 @@ struct guac_stream { * * Example: * @code - * int blob_handler(guac_client* client, guac_stream* stream, + * int blob_handler(guac_user* user, guac_stream* stream, * void* data, int length); * - * int my_clipboard_handler(guac_client* client, guac_stream* stream, + * int my_clipboard_handler(guac_user* user, guac_stream* stream, * char* mimetype) { * stream->blob_handler = blob_handler; * } * @endcode */ - guac_client_blob_handler* blob_handler; + guac_user_blob_handler* blob_handler; /** * Handler for stream end events sent by the Guacamole web-client. @@ -98,15 +98,15 @@ struct guac_stream { * * Example: * @code - * int end_handler(guac_client* client, guac_stream* stream); + * int end_handler(guac_user* user, guac_stream* stream); * - * int my_clipboard_handler(guac_client* client, guac_stream* stream, + * int my_clipboard_handler(guac_user* user, guac_stream* stream, * char* mimetype) { * stream->end_handler = end_handler; * } * @endcode */ - guac_client_end_handler* end_handler; + guac_user_end_handler* end_handler; }; diff --git a/src/libguac/guacamole/timestamp.h b/src/libguac/guacamole/timestamp.h index 05bb0f83..fae38193 100644 --- a/src/libguac/guacamole/timestamp.h +++ b/src/libguac/guacamole/timestamp.h @@ -37,9 +37,18 @@ * calls. The return value from a single call will not have any useful * (or defined) meaning. * - * @return An arbitrary millisecond timestamp. + * @return + * An arbitrary millisecond timestamp. */ guac_timestamp guac_timestamp_current(); +/** + * Sleeps for the given number of milliseconds. + * + * @param duration + * The number of milliseconds to sleep. + */ +void guac_timestamp_msleep(int duration); + #endif diff --git a/src/libguac/guacamole/user-constants.h b/src/libguac/guacamole/user-constants.h new file mode 100644 index 00000000..68106020 --- /dev/null +++ b/src/libguac/guacamole/user-constants.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _GUAC_USER_CONSTANTS_H +#define _GUAC_USER_CONSTANTS_H + +/** + * Constants related to the Guacamole user structure, guac_user. + * + * @file user-constants.h + */ + +/** + * The character prefix which identifies a user ID. + */ +#define GUAC_USER_ID_PREFIX '@' + +/** + * The maximum number of inbound or outbound streams supported by any one + * guac_user. + */ +#define GUAC_USER_MAX_STREAMS 64 + +/** + * The index of a closed stream. + */ +#define GUAC_USER_CLOSED_STREAM_INDEX -1 + +/** + * The maximum number of objects supported by any one guac_client. + */ +#define GUAC_USER_MAX_OBJECTS 64 + +/** + * The index of an object which has not been defined. + */ +#define GUAC_USER_UNDEFINED_OBJECT_INDEX -1 + +/** + * The stream name reserved for the root of a Guacamole protocol object. + */ +#define GUAC_USER_OBJECT_ROOT_NAME "/" + +/** + * The mimetype of a stream containing a map of available stream names to their + * corresponding mimetypes. The root of a Guacamole protocol object is + * guaranteed to have this type. + */ +#define GUAC_USER_STREAM_INDEX_MIMETYPE "application/vnd.glyptodon.guacamole.stream-index+json" + +#endif + diff --git a/src/libguac/guacamole/user-fntypes.h b/src/libguac/guacamole/user-fntypes.h new file mode 100644 index 00000000..228b4cfe --- /dev/null +++ b/src/libguac/guacamole/user-fntypes.h @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _GUAC_USER_FNTYPES_H +#define _GUAC_USER_FNTYPES_H + +/** + * Function type definitions related to the guac_user object. + * + * @file user-fntypes.h + */ + +#include "object-types.h" +#include "protocol-types.h" +#include "stream-types.h" +#include "timestamp-types.h" +#include "user-types.h" + +/** + * Callback which relates to a single guac_user at a time, along with arbitrary + * data. + * + * @see guac_client_foreach_user() + * @see guac_client_for_owner() + * + * @param user + * The user for which this callback was invoked. Depending on whether + * guac_client_foreach_user() or guac_client_for_owner() was called, this + * will either be the current user as the "foreach" iteration continues, + * or the owner of the connection. If guac_client_for_owner() was called + * for a connection which has no owner, this may be NULL. + * + * @param data + * The arbitrary data passed to guac_client_foreach_user() or + * guac_client_for_owner(). + * + * @return + * An arbitrary return value, the semantics of which are determined by the + * implementation of the callback and the manner of its user. In the case + * of a callback provided to guac_client_foreach_user(), this value is + * always discarded. + */ +typedef void* guac_user_callback(guac_user* user, void* data); + +/** + * Handler for Guacamole mouse events, invoked when a "mouse" instruction has + * been received from a user. + * + * @param user + * The user that sent the mouse event. + * + * @param x + * The X coordinate of the mouse within the display when the event + * occurred, in pixels. This value is not guaranteed to be within the + * bounds of the display area. + * + * @param y + * The Y coordinate of the mouse within the display when the event + * occurred, in pixels. This value is not guaranteed to be within the + * bounds of the display area. + * + * @param button_mask + * An integer value representing the current state of each button, where + * the Nth bit within the integer is set to 1 if and only if the Nth mouse + * button is currently pressed. The lowest-order bit is the left mouse + * button, followed by the middle button, right button, and finally the up + * and down buttons of the scroll wheel. + * + * @see GUAC_CLIENT_MOUSE_LEFT + * @see GUAC_CLIENT_MOUSE_MIDDLE + * @see GUAC_CLIENT_MOUSE_RIGHT + * @see GUAC_CLIENT_MOUSE_SCROLL_UP + * @see GUAC_CLIENT_MOUSE_SCROLL_DOWN + * + * @return + * Zero if the mouse event was handled successfully, or non-zero if an + * error occurred. + */ +typedef int guac_user_mouse_handler(guac_user* user, int x, int y, + int button_mask); + +/** + * Handler for Guacamole key events, invoked when a "key" event has been + * received from a user. + * + * @param user + * The user that sent the key event. + * + * @param keysym + * The X11 keysym of the key that was pressed or released. + * + * @param pressed + * Non-zero if the key represented by the given keysym is currently + * pressed, zero if it is released. + * + * @return + * Zero if the key event was handled successfully, or non-zero if an error + * occurred. + */ +typedef int guac_user_key_handler(guac_user* user, int keysym, int pressed); + +/** + * Handler for Guacamole clipboard streams received from a user. Each such + * clipboard stream begins when the user sends a "clipboard" instruction. To + * handle received data along this stream, implementations of this handler + * must assign blob and end handlers to the given stream object. + * + * @param user + * The user that opened the clipboard stream. + * + * @param stream + * The stream object allocated by libguac to represent the clipboard stream + * opened by the user. + * + * @param mimetype + * The mimetype of the data that will be sent along the stream. + * + * @return + * Zero if the opening of the clipboard stream has been handled + * successfully, or non-zero if an error occurs. + */ +typedef int guac_user_clipboard_handler(guac_user* user, guac_stream* stream, + char* mimetype); + +/** + * Handler for Guacamole size events, invoked when a "size" instruction has + * been received from a user. A "size" instruction indicates that the desired + * display size has changed. + * + * @param user + * The user whose desired display size has changed. + * + * @param width + * The desired width of the display, in pixels. + * + * @param height + * The desired height of the display, in pixels. + * + * @return + * Zero if the size event has been successfully handled, non-zero + * otherwise. + */ +typedef int guac_user_size_handler(guac_user* user, + int width, int height); + +/** + * Handler for Guacamole file streams received from a user. Each such file + * stream begins when the user sends a "file" instruction. To handle received + * data along this stream, implementations of this handler must assign blob and + * end handlers to the given stream object. + * + * @param user + * The user that opened the file stream. + * + * @param stream + * The stream object allocated by libguac to represent the file stream + * opened by the user. + * + * @param mimetype + * The mimetype of the data that will be sent along the stream. + * + * @param filename + * The name of the file being transferred. + * + * @return + * Zero if the opening of the file stream has been handled successfully, or + * non-zero if an error occurs. + */ +typedef int guac_user_file_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* filename); + +/** + * Handler for Guacamole pipe streams received from a user. Pipe streams are + * unidirectional, arbitrary, named pipes. Each such pipe stream begins when + * the user sends a "pipe" instruction. To handle received data along this + * stream, implementations of this handler must assign blob and end handlers to + * the given stream object. + * + * @param user + * The user that opened the pipe stream. + * + * @param stream + * The stream object allocated by libguac to represent the pipe stream + * opened by the user. + * + * @param mimetype + * The mimetype of the data that will be sent along the stream. + * + * @param name + * The arbitrary name assigned to this pipe. It is up to the implementation + * of this handler and the application containing the Guacamole client to + * determine the semantics of a pipe stream having this name. + * + * @return + * Zero if the opening of the pipe stream has been handled successfully, or + * non-zero if an error occurs. + */ +typedef int guac_user_pipe_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name); + +/** + * Handler for Guacamole stream blobs. Each blob originates from a "blob" + * instruction which was associated with a previously-created stream. + * + * @param user + * The user that is sending this blob of data along the stream. + * + * @param stream + * The stream along which the blob was received. The semantics associated + * with this stream are determined by the manner of its creation. + * + * @param data + * The blob of data received. + * + * @param length + * The number of bytes within the blob of data received. + * + * @return + * Zero if the blob of data was successfully handled, non-zero otherwise. + */ +typedef int guac_user_blob_handler(guac_user* user, guac_stream* stream, + void* data, int length); + +/** + * Handler for Guacamole stream "ack" instructions. A user will send "ack" + * instructions to acknowledge the successful receipt of blobs along a stream + * opened by the server, or to notify of errors. An "ack" with an error status + * implicitly closes the stream. + * + * @param user + * The user sending the "ack" instruction. + * + * @param stream + * The stream for which the "ack" was received. + * + * @param error + * An arbitrary, human-readable message describing the error that + * occurred, if any. If no error occurs, this will likely be blank, + * "SUCCESS", or similar. This value exists for the sake of readability, + * not for the sake of data interchange. + * + * @param status + * GUAC_PROTOCOL_STATUS_SUCCESS if the blob was received and handled + * successfully, or a different status code describing the problem if an + * error occurred and the stream has been implicitly closed. + * + * @return + * Zero if the "ack" message was successfully handled, non-zero otherwise. + */ +typedef int guac_user_ack_handler(guac_user* user, guac_stream* stream, + char* error, guac_protocol_status status); + +/** + * Handler for Guacamole stream "end" instructions. End instructions are sent + * by the user when a stream is closing because its end has been reached. + * + * @param user + * The user that sent the "end" instruction. + * + * @param stream + * The stream that is being closed. + * + * @return + * Zero if the end-of-stream condition has been sucessfully handled, + * non-zero otherwise. + */ +typedef int guac_user_end_handler(guac_user* user, guac_stream* stream); + +/** + * Handler for Guacamole join events. A join event is fired by the + * guac_client whenever a guac_user joins the connection. There is no + * instruction associated with a join event. + * + * Implementations of the join handler MUST NOT use the client-level + * broadcast socket, nor invoke guac_client_foreach_user() or + * guac_client_for_owner(). Doing so will result in undefined behavior, + * including segfaults. + * + * @param user + * The user joining the connection. The guac_client associated with the + * connection will already be populated within the user object. + * + * @param argc + * The number of arguments stored within argv. + * + * @param argv + * An array of all arguments provided by the user when they joined. These + * arguments must correspond to the argument names declared when the + * guac_client was initialized. If the number of arguments does not match + * the number of argument names declared, then the joining user has + * violated the Guacamole protocol. + * + * @return + * Zero if the user has been successfully initialized and should be allowed + * to join the connection, non-zero otherwise. + */ +typedef int guac_user_join_handler(guac_user* user, int argc, char** argv); + +/** + * Handler for Guacamole leave events. A leave event is fired by the + * guac_client whenever a guac_user leaves the connection. There is no + * instruction associated with a leave event. + * + * Implementations of the leave handler MUST NOT use the client-level + * broadcast socket, nor invoke guac_client_foreach_user() or + * guac_client_for_owner(). Doing so will result in undefined behavior, + * including segfaults. + * + * @param user + * The user that has left the connection. + * + * @return + * Zero if the leave event has been successfully handled, non-zero + * otherwise. + */ +typedef int guac_user_leave_handler(guac_user* user); + +/** + * Handler for Guacamole sync events. A sync event is fired by the + * guac_client whenever a guac_user responds to a "sync" instruction. Sync + * instructions are sent by the Guacamole server to mark the logical end of a + * frame, and to inform the Guacamole client that all data up to a particular + * point in time has been sent. The response from the Guacamole client + * similarly indicates that all data received up to a particular point in + * server time has been handled. + * + * @param user + * The user that sent the "sync" instruction. + * + * @param timestamp + * The timestamp contained within the sync instruction. + * + * @return + * Zero if the sync event has been handled successfully, non-zero + * otherwise. + */ +typedef int guac_user_sync_handler(guac_user* user, guac_timestamp timestamp); + +/** + * Handler for Guacamole object get requests. The semantics of the stream + * which will be created in response to the request are determined by the type + * of the object and the name of the stream requested. It is up to the + * implementation of this handler to then respond with a "body" instruction + * that begins the requested stream. + * + * @param user + * The user requesting read access to the stream having the given name. + * + * @param object + * The object from which the given named stream is being requested. + * + * @param name + * The name of the stream being requested. + * + * @return + * Zero if the get request was successfully handled, non-zero otherwise. + */ +typedef int guac_user_get_handler(guac_user* user, guac_object* object, + char* name); + +/** + * Handler for Guacamole object put requests. Put requests implicitly create a + * stream, the semantics of which are determined by the type of the object + * and the name of the stream requested. + * + * @param user + * The user requesting write access to the stream having the given name. + * + * @param object + * The object from which the given named stream is being requested. + * + * @param stream + * The stream along which the blobs which should be written to the named + * stream will be received. + * + * @param mimetype + * The mimetype of the data that will be received along the given stream. + * + * @param name + * The name of the stream being requested. + * + * @return + * Zero if the put request was successfully handled, non-zero otherwise. + */ +typedef int guac_user_put_handler(guac_user* user, guac_object* object, + guac_stream* stream, char* mimetype, char* name); + +#endif + diff --git a/src/libguac/guacamole/plugin-types.h b/src/libguac/guacamole/user-types.h similarity index 70% rename from src/libguac/guacamole/plugin-types.h rename to src/libguac/guacamole/user-types.h index 69c9cc07..ff293f18 100644 --- a/src/libguac/guacamole/plugin-types.h +++ b/src/libguac/guacamole/user-types.h @@ -20,21 +20,26 @@ * THE SOFTWARE. */ -#ifndef _GUAC_PLUGIN_TYPES_H -#define _GUAC_PLUGIN_TYPES_H +#ifndef _GUAC_USER_TYPES_H +#define _GUAC_USER_TYPES_H /** - * Type definitions related to client plugins. + * Type definitions related to the guac_user object. * - * @file plugin-types.h + * @file user-types.h */ /** - * A handle to a client plugin, containing enough information about the - * plugin to complete the initial protocol handshake and instantiate a new - * client supporting the protocol provided by the client plugin. + * Representation of a physical connection within a larger logical connection + * which may be shared. Logical connections are represented by guac_client. */ -typedef struct guac_client_plugin guac_client_plugin; +typedef struct guac_user guac_user; + +/** + * Information exposed by the remote client during the connection handshake + * which can be used by a client plugin. + */ +typedef struct guac_user_info guac_user_info; #endif diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h new file mode 100644 index 00000000..21cdd1ac --- /dev/null +++ b/src/libguac/guacamole/user.h @@ -0,0 +1,847 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _GUAC_USER_H +#define _GUAC_USER_H + +/** + * Defines the guac_user object, which represents a physical connection + * within a larger, possibly shared, logical connection represented by a + * guac_client. + * + * @file user.h + */ + +#include "client-types.h" +#include "layer-types.h" +#include "pool-types.h" +#include "socket-types.h" +#include "stream-types.h" +#include "timestamp-types.h" +#include "user-constants.h" +#include "user-fntypes.h" +#include "user-types.h" + +#include + +#include +#include + +struct guac_user_info { + + /** + * The number of pixels the remote client requests for the display width. + * This need not be honored by a client plugin implementation, but if the + * underlying protocol of the client plugin supports dynamic sizing of the + * screen, honoring the display size request is recommended. + */ + int optimal_width; + + /** + * The number of pixels the remote client requests for the display height. + * This need not be honored by a client plugin implementation, but if the + * underlying protocol of the client plugin supports dynamic sizing of the + * screen, honoring the display size request is recommended. + */ + int optimal_height; + + /** + * NULL-terminated array of client-supported audio mimetypes. If the client + * does not support audio at all, this will be NULL. + */ + const char** audio_mimetypes; + + /** + * NULL-terminated array of client-supported video mimetypes. If the client + * does not support video at all, this will be NULL. + */ + const char** video_mimetypes; + + /** + * NULL-terminated array of client-supported image mimetypes. Though all + * supported image mimetypes will be listed here, it can be safely assumed + * that all clients will support at least "image/png" and "image/jpeg". + */ + const char** image_mimetypes; + + /** + * The DPI of the physical remote display if configured for the optimal + * width/height combination described here. This need not be honored by + * a client plugin implementation, but if the underlying protocol of the + * client plugin supports dynamic sizing of the screen, honoring the + * stated resolution of the display size request is recommended. + */ + int optimal_resolution; + +}; + +struct guac_user { + + /** + * The guac_client to which this user belongs. + */ + guac_client* client; + + /** + * This user's actual socket. Data written to this socket will + * be received by this user alone, and data sent by this specific + * user will be received by this socket. + */ + guac_socket* socket; + + /** + * The unique identifier allocated for this user, which may be used within + * the Guacamole protocol to refer to this user. This identifier is + * guaranteed to be unique from all existing connections and users, and + * will not collide with any available protocol names. + */ + char* user_id; + + /** + * Non-zero if this user is the owner of the associated connection, zero + * otherwise. The owner is the user which created the connection. + */ + int owner; + + /** + * Non-zero if this user is active (connected), and zero otherwise. When + * the user is created, this will be set to a non-zero value. If an event + * occurs which requires that the user disconnect, or the user has + * disconnected, this will be reset to zero. + */ + int active; + + /** + * The previous user in the group of users within the same logical + * connection. This is currently only used internally by guac_client to + * track its set of connected users. To iterate connected users, use + * guac_client_foreach_user(). + */ + guac_user* __prev; + + /** + * The next user in the group of users within the same logical connection. + * This is currently only used internally by guac_client to track its set + * of connected users. To iterate connected users, use + * guac_client_foreach_user(). + */ + guac_user* __next; + + /** + * The time (in milliseconds) of receipt of the last sync message from + * the user. + */ + guac_timestamp last_received_timestamp; + + /** + * The duration of the last frame rendered by the user, in milliseconds. + * This duration will include network and processing lag, and thus should + * be slightly higher than the true frame duration. + */ + int last_frame_duration; + + /** + * The overall lag experienced by the user relative to the stream of + * frames, roughly excluding network lag. + */ + int processing_lag; + + /** + * Information structure containing properties exposed by the remote + * user during the initial handshake process. + */ + guac_user_info info; + + /** + * Pool of stream indices. + */ + guac_pool* __stream_pool; + + /** + * All available output streams (data going to connected user). + */ + guac_stream* __output_streams; + + /** + * All available input streams (data coming from connected user). + */ + guac_stream* __input_streams; + + /** + * Pool of object indices. + */ + guac_pool* __object_pool; + + /** + * All available objects (arbitrary sets of named streams). + */ + guac_object* __objects; + + /** + * Arbitrary user-specific data. + */ + void* data; + + /** + * Handler for mouse events sent by the Gaucamole web-client. + * + * The handler takes the integer mouse X and Y coordinates, as well as + * a button mask containing the bitwise OR of all button values currently + * being pressed. Those values are: + * + * + * + * + * + * + * + * + *
Button Value
Left 1
Middle 2
Right 4
Scrollwheel Up 8
Scrollwheel Down16
+ + * Example: + * @code + * int mouse_handler(guac_user* user, int x, int y, int button_mask); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->mouse_handler = mouse_handler; + * } + * @endcode + */ + guac_user_mouse_handler* mouse_handler; + + /** + * Handler for key events sent by the Guacamole web-client. + * + * The handler takes the integer X11 keysym associated with the key + * being pressed/released, and an integer representing whether the key + * is being pressed (1) or released (0). + * + * Example: + * @code + * int key_handler(guac_user* user, int keysym, int pressed); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->key_handler = key_handler; + * } + * @endcode + */ + guac_user_key_handler* key_handler; + + /** + * Handler for clipboard events sent by the Guacamole web-client. This + * handler will be called whenever the web-client sets the data of the + * clipboard. + * + * The handler takes a guac_stream, which contains the stream index and + * will persist through the duration of the transfer, and the mimetype + * of the data being transferred. + * + * Example: + * @code + * int clipboard_handler(guac_user* user, guac_stream* stream, + * char* mimetype); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->clipboard_handler = clipboard_handler; + * } + * @endcode + */ + guac_user_clipboard_handler* clipboard_handler; + + /** + * Handler for size events sent by the Guacamole web-client. + * + * The handler takes an integer width and integer height, representing + * the current visible screen area of the client. + * + * Example: + * @code + * int size_handler(guac_user* user, int width, int height); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->size_handler = size_handler; + * } + * @endcode + */ + guac_user_size_handler* size_handler; + + /** + * Handler for file events sent by the Guacamole web-client. + * + * The handler takes a guac_stream which contains the stream index and + * will persist through the duration of the transfer, the mimetype of + * the file being transferred, and the filename. + * + * Example: + * @code + * int file_handler(guac_user* user, guac_stream* stream, + * char* mimetype, char* filename); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->file_handler = file_handler; + * } + * @endcode + */ + guac_user_file_handler* file_handler; + + /** + * Handler for pipe events sent by the Guacamole web-client. + * + * The handler takes a guac_stream which contains the stream index and + * will persist through the duration of the transfer, the mimetype of + * the data being transferred, and the pipe name. + * + * Example: + * @code + * int pipe_handler(guac_user* user, guac_stream* stream, + * char* mimetype, char* name); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->pipe_handler = pipe_handler; + * } + * @endcode + */ + guac_user_pipe_handler* pipe_handler; + + /** + * Handler for ack events sent by the Guacamole web-client. + * + * The handler takes a guac_stream which contains the stream index and + * will persist through the duration of the transfer, a string containing + * the error or status message, and a status code. + * + * Example: + * @code + * int ack_handler(guac_user* user, guac_stream* stream, + * char* error, guac_protocol_status status); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->ack_handler = ack_handler; + * } + * @endcode + */ + guac_user_ack_handler* ack_handler; + + /** + * Handler for blob events sent by the Guacamole web-client. + * + * The handler takes a guac_stream which contains the stream index and + * will persist through the duration of the transfer, an arbitrary buffer + * containing the blob, and the length of the blob. + * + * Example: + * @code + * int blob_handler(guac_user* user, guac_stream* stream, + * void* data, int length); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->blob_handler = blob_handler; + * } + * @endcode + */ + guac_user_blob_handler* blob_handler; + + /** + * Handler for stream end events sent by the Guacamole web-client. + * + * The handler takes only a guac_stream which contains the stream index. + * This guac_stream will be disposed of immediately after this event is + * finished. + * + * Example: + * @code + * int end_handler(guac_user* user, guac_stream* stream); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->end_handler = end_handler; + * } + * @endcode + */ + guac_user_end_handler* end_handler; + + /** + * Handler for sync events sent by the Guacamole web-client. Sync events + * are used to track per-user latency. + * + * The handler takes only a guac_timestamp which contains the timestamp + * received from the user. Latency can be determined by comparing this + * timestamp against the last_sent_timestamp of guac_client. + * + * Example: + * @code + * int sync_handler(guac_user* user, guac_timestamp timestamp); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->sync_handler = sync_handler; + * } + * @endcode + */ + guac_user_sync_handler* sync_handler; + + /** + * Handler for leave events fired by the guac_client when a guac_user + * is leaving an active connection. + * + * The handler takes only a guac_user which will be the guac_user that + * left the connection. This guac_user will be disposed of immediately + * after this event is finished. + * + * Example: + * @code + * int leave_handler(guac_user* user); + * + * int my_join_handler(guac_user* user, int argv, char** argv) { + * user->leave_handler = leave_handler; + * } + * @endcode + */ + guac_user_leave_handler* leave_handler; + + /** + * Handler for get events sent by the Guacamole web-client. + * + * The handler takes a guac_object, containing the object index which will + * persist through the duration of the transfer, and the name of the stream + * being requested. It is up to the get handler to create the required body + * stream. + * + * Example: + * @code + * int get_handler(guac_user* user, guac_object* object, + * char* name); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->get_handler = get_handler; + * } + * @endcode + */ + guac_user_get_handler* get_handler; + + /** + * Handler for put events sent by the Guacamole web-client. + * + * The handler takes a guac_object and guac_stream, which each contain their + * respective indices which will persist through the duration of the + * transfer, the mimetype of the data being transferred, and the name of + * the stream within the object being written to. + * + * Example: + * @code + * int put_handler(guac_user* user, guac_object* object, + * guac_stream* stream, char* mimetype, char* name); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->put_handler = put_handler; + * } + * @endcode + */ + guac_user_put_handler* put_handler; + +}; + +/** + * Allocates a new, blank user, not associated with any specific client or + * socket. + * + * @return The newly allocated guac_user, or NULL if allocation failed. + */ +guac_user* guac_user_alloc(); + +/** + * Frees the given user and all associated resources. + * + * @param user The guac_user to free. + */ +void guac_user_free(guac_user* user); + +/** + * Call the appropriate handler defined by the given user for the given + * instruction. A comparison is made between the instruction opcode and the + * initial handler lookup table defined in user-handlers.c. The intial handlers + * will in turn call the user's handler (if defined). + * + * @param user + * The user whose handlers should be called. + * + * @param opcode + * The opcode of the instruction to pass to the user via the appropriate + * handler. + * + * @param argc + * The number of arguments which are part of the instruction. + * + * @param argv + * An array of all arguments which are part of the instruction. + * + * @return + * Non-negative if the instruction was handled successfully, or negative + * if an error occurred. + */ +int guac_user_handle_instruction(guac_user* user, const char* opcode, + int argc, char** argv); + +/** + * Allocates a new stream. An arbitrary index is automatically assigned + * if no previously-allocated stream is available for use. + * + * @param user The user to allocate the stream for. + * @return The next available stream, or a newly allocated stream. + */ +guac_stream* guac_user_alloc_stream(guac_user* user); + +/** + * Returns the given stream to the pool of available streams, such that it + * can be reused by any subsequent call to guac_user_alloc_stream(). + * + * @param user The user to return the stream to. + * @param stream The stream to return to the pool of available stream. + */ +void guac_user_free_stream(guac_user* user, guac_stream* stream); + +/** + * Signals the given user that it must disconnect, or advises cooperating + * services that the given user is no longer connected. + * + * @param user The user to stop. + */ +void guac_user_stop(guac_user* user); + +/** + * Signals the given user to stop gracefully, while also signalling via the + * Guacamole protocol that an error has occurred. Note that this is a completely + * cooperative signal, and can be ignored by the user or the hosting + * daemon. The message given will be logged to the system logs. + * + * @param user The user to signal to stop. + * @param status The status to send over the Guacamole protocol. + * @param format A printf-style format string to log. + * @param ... Arguments to use when filling the format string for printing. + */ +void guac_user_abort(guac_user* user, guac_protocol_status status, + const char* format, ...); + +/** + * Signals the given user to stop gracefully, while also signalling via the + * Guacamole protocol that an error has occurred. Note that this is a completely + * cooperative signal, and can be ignored by the user or the hosting + * daemon. The message given will be logged to the system logs. + * + * @param user The user to signal to stop. + * @param status The status to send over the Guacamole protocol. + * @param format A printf-style format string to log. + * @param ap The va_list containing the arguments to be used when filling the + * format string for printing. + */ +void vguac_user_abort(guac_user* user, guac_protocol_status status, + const char* format, va_list ap); + +/** + * Writes a message in the log used by the given user. The logger used will + * normally be defined by guacd (or whichever program loads the user) + * by setting the logging handlers of the user when it is loaded. + * + * @param user The user logging this message. + * @param level The level at which to log this message. + * @param format A printf-style format string to log. + * @param ... Arguments to use when filling the format string for printing. + */ +void guac_user_log(guac_user* user, guac_client_log_level level, + const char* format, ...); + +/** + * Writes a message in the log used by the given user. The logger used will + * normally be defined by guacd (or whichever program loads the user) + * by setting the logging handlers of the user when it is loaded. + * + * @param user The user logging this message. + * @param level The level at which to log this message. + * @param format A printf-style format string to log. + * @param ap The va_list containing the arguments to be used when filling the + * format string for printing. + */ +void vguac_user_log(guac_user* user, guac_client_log_level level, + const char* format, va_list ap); + +/** + * Allocates a new object. An arbitrary index is automatically assigned + * if no previously-allocated object is available for use. + * + * @param user + * The user to allocate the object for. + * + * @return + * The next available object, or a newly allocated object. + */ +guac_object* guac_user_alloc_object(guac_user* user); + +/** + * Returns the given object to the pool of available objects, such that it + * can be reused by any subsequent call to guac_user_alloc_object(). + * + * @param user + * The user to return the object to. + * + * @param object + * The object to return to the pool of available object. + */ +void guac_user_free_object(guac_user* user, guac_object* object); + +/** + * Streams the image data of the given surface over an image stream ("img" + * instruction) as PNG-encoded data. The image stream will be automatically + * allocated and freed. + * + * @param user + * The Guacamole user for whom the image stream should be allocated. + * + * @param socket + * The socket over which instructions associated with the image stream + * should be sent. + * + * @param mode + * The composite mode to use when rendering the image over the given layer. + * + * @param layer + * The destination layer. + * + * @param x + * The X coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param y + * The Y coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param surface + * A Cairo surface containing the image data to be streamed. + */ +void guac_user_stream_png(guac_user* user, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface); + +/** + * Streams the image data of the given surface over an image stream ("img" + * instruction) as JPEG-encoded data at the given quality. The image stream + * will be automatically allocated and freed. + * + * @param user + * The Guacamole user for whom the image stream should be allocated. + * + * @param socket + * The socket over which instructions associated with the image stream + * should be sent. + * + * @param mode + * The composite mode to use when rendering the image over the given layer. + * + * @param layer + * The destination layer. + * + * @param x + * The X coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param y + * The Y coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param surface + * A Cairo surface containing the image data to be streamed. + * + * @param quality + * The JPEG image quality, which must be an integer value between 0 and 100 + * inclusive. Larger values indicate improving quality at the expense of + * larger file size. + */ +void guac_user_stream_jpeg(guac_user* user, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface, int quality); + +/** + * Streams the image data of the given surface over an image stream ("img" + * instruction) as WebP-encoded data at the given quality. The image stream + * will be automatically allocated and freed. If the server does not support + * WebP, this function has no effect, so be sure to check the result of + * guac_user_supports_webp() or guac_client_supports_webp() prior to calling + * this function. + * + * @param user + * The Guacamole user for whom the image stream should be allocated. + * + * @param socket + * The socket over which instructions associated with the image stream + * should be sent. + * + * @param mode + * The composite mode to use when rendering the image over the given layer. + * + * @param layer + * The destination layer. + * + * @param x + * The X coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param y + * The Y coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param surface + * A Cairo surface containing the image data to be streamed. + * + * @param quality + * The WebP image quality, which must be an integer value between 0 and 100 + * inclusive. For lossy images, larger values indicate improving quality at + * the expense of larger file size. For lossless images, this dictates the + * quality of compression, with larger values producing smaller files at + * the expense of speed. + * + * @param lossless + * Zero to encode a lossy image, non-zero to encode losslessly. + */ +void guac_user_stream_webp(guac_user* user, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface, int quality, int lossless); + +/** + * Returns whether the given user supports WebP. If the user does not + * support WebP, or the server cannot encode WebP images, zero is returned. + * + * @param user + * The Guacamole user to check for WebP support. + * + * @return + * Non-zero if the given user claims to support WebP and the server has + * been built with WebP support, zero otherwise. + */ +int guac_user_supports_webp(guac_user* user); + +/** + * Automatically handles a single argument received from a joining user, + * returning a newly-allocated string containing that value. If the argument + * provided by the user is blank, a newly-allocated string containing the + * default value is returned. + * + * @param user + * The user joining the connection and providing the given arguments. + * + * @param arg_names + * A NULL-terminated array of argument names, corresponding to the provided + * array of argument values. This array must be exactly the same size as + * the argument value array, with one additional entry for the NULL + * terminator. + * + * @param argv + * An array of all argument values, corresponding to the provided array of + * argument names. This array must be exactly the same size as the argument + * name array, with the exception of the NULL terminator. + * + * @param index + * The index of the entry in both the arg_names and argv arrays which + * corresponds to the argument being parsed. + * + * @param default_value + * The value to return if the provided argument is blank. If this value is + * not NULL, the returned value will be a newly-allocated string containing + * this value. + * + * @return + * A newly-allocated string containing the provided argument value. If the + * provided argument value is blank, this newly-allocated string will + * contain the default value. If the default value is NULL and the provided + * argument value is blank, no string will be allocated and NULL is + * returned. + */ +char* guac_user_parse_args_string(guac_user* user, const char** arg_names, + const char** argv, int index, const char* default_value); + +/** + * Automatically handles a single integer argument received from a joining + * user, returning the integer value of that argument. If the argument provided + * by the user is blank or invalid, the default value is returned. + * + * @param user + * The user joining the connection and providing the given arguments. + * + * @param arg_names + * A NULL-terminated array of argument names, corresponding to the provided + * array of argument values. This array must be exactly the same size as + * the argument value array, with one additional entry for the NULL + * terminator. + * + * @param argv + * An array of all argument values, corresponding to the provided array of + * argument names. This array must be exactly the same size as the argument + * name array, with the exception of the NULL terminator. + * + * @param index + * The index of the entry in both the arg_names and argv arrays which + * corresponds to the argument being parsed. + * + * @param default_value + * The value to return if the provided argument is blank or invalid. + * + * @return + * The integer value parsed from the provided argument value, or the + * default value if the provided argument value is blank or invalid. + */ +int guac_user_parse_args_int(guac_user* user, const char** arg_names, + const char** argv, int index, int default_value); + +/** + * Automatically handles a single boolean argument received from a joining + * user, returning the value of that argument (either 1 for true or 0 for + * false). Only "true" and "false" are legitimate values for a boolean + * argument. If the argument provided by the user is blank or invalid, the + * default value is returned. + * + * @param user + * The user joining the connection and providing the given arguments. + * + * @param arg_names + * A NULL-terminated array of argument names, corresponding to the provided + * array of argument values. This array must be exactly the same size as + * the argument value array, with one additional entry for the NULL + * terminator. + * + * @param argv + * An array of all argument values, corresponding to the provided array of + * argument names. This array must be exactly the same size as the argument + * name array, with the exception of the NULL terminator. + * + * @param index + * The index of the entry in both the arg_names and argv arrays which + * corresponds to the argument being parsed. + * + * @param default_value + * The value to return if the provided argument is blank or invalid. + * + * @return + * true (1) if the provided argument value is "true", false (0) if the + * provided argument value is "false", or the default value if the provided + * argument value is blank or invalid. + */ +int guac_user_parse_args_boolean(guac_user* user, const char** arg_names, + const char** argv, int index, int default_value); + +#endif + diff --git a/src/libguac/id.c b/src/libguac/id.c new file mode 100644 index 00000000..dabf8f6e --- /dev/null +++ b/src/libguac/id.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "id.h" +#include "error.h" + +#ifdef HAVE_OSSP_UUID_H +#include +#else +#include +#endif + +#include + +char* guac_generate_id(char prefix) { + + char* buffer; + char* identifier; + size_t identifier_length; + + uuid_t* uuid; + + /* Attempt to create UUID object */ + if (uuid_create(&uuid) != UUID_RC_OK) { + guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for UUID"; + return NULL; + } + + /* Generate random UUID */ + if (uuid_make(uuid, UUID_MAKE_V4) != UUID_RC_OK) { + uuid_destroy(uuid); + guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "UUID generation failed"; + return NULL; + } + + /* Allocate buffer for future formatted ID */ + buffer = malloc(UUID_LEN_STR + 2); + if (buffer == NULL) { + uuid_destroy(uuid); + guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Could not allocate memory for connection ID"; + return NULL; + } + + identifier = &(buffer[1]); + identifier_length = UUID_LEN_STR + 1; + + /* Build connection ID from UUID */ + if (uuid_export(uuid, UUID_FMT_STR, &identifier, &identifier_length) != UUID_RC_OK) { + free(buffer); + uuid_destroy(uuid); + guac_error = GUAC_STATUS_INTERNAL_ERROR; + guac_error_message = "Conversion of UUID to connection ID failed"; + return NULL; + } + + uuid_destroy(uuid); + + buffer[0] = prefix; + buffer[UUID_LEN_STR + 1] = '\0'; + return buffer; + +} + + diff --git a/src/libguac/id.h b/src/libguac/id.h new file mode 100644 index 00000000..a8be2ec2 --- /dev/null +++ b/src/libguac/id.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __GUAC_ID_H +#define __GUAC_ID_H + +/** + * Generates a guaranteed-unique identifier which is a total of 37 characters + * long, having the given single-character prefix. The resulting identifier + * must be freed with a call to free() when no longer needed. If an error + * occurs, NULL is returned, no memory is allocated, and guac_error is set + * appropriately. + * + * @param prefix The single-character prefix to use. + * @return A newly-allocated unique identifier with the given prefix, or + * NULL if the identifier could not be generated. + */ +char* guac_generate_id(char prefix); + +#endif + diff --git a/src/libguac/instruction.c b/src/libguac/parser.c similarity index 55% rename from src/libguac/instruction.c rename to src/libguac/parser.c index 90f9067d..3c728a18 100644 --- a/src/libguac/instruction.c +++ b/src/libguac/parser.c @@ -23,7 +23,7 @@ #include "config.h" #include "error.h" -#include "instruction.h" +#include "parser.h" #include "socket.h" #include "unicode.h" @@ -31,46 +31,49 @@ #include #include -guac_instruction* guac_instruction_alloc() { +static void guac_parser_reset(guac_parser* parser) { + parser->opcode = NULL; + parser->argc = 0; + parser->state = GUAC_PARSE_LENGTH; + parser->__elementc = 0; + parser->__element_length = 0; +} - /* Allocate space for instruction */ - guac_instruction* instruction = malloc(sizeof(guac_instruction)); - if (instruction == NULL) { +guac_parser* guac_parser_alloc() { + + /* Allocate space for parser */ + guac_parser* parser = malloc(sizeof(guac_parser)); + if (parser == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; - guac_error_message = "Insufficient memory to allocate instruction"; + guac_error_message = "Insufficient memory to allocate parser"; return NULL; } - guac_instruction_reset(instruction); - return instruction; + /* Init parse start/end markers */ + parser->__instructionbuf_unparsed_start = parser->__instructionbuf; + parser->__instructionbuf_unparsed_end = parser->__instructionbuf; + + guac_parser_reset(parser); + return parser; } -void guac_instruction_reset(guac_instruction* instruction) { - instruction->opcode = NULL; - instruction->argc = 0; - instruction->state = GUAC_INSTRUCTION_PARSE_LENGTH; - instruction->__elementc = 0; - instruction->__element_length = 0; -} - -int guac_instruction_append(guac_instruction* instr, - void* buffer, int length) { +int guac_parser_append(guac_parser* parser, void* buffer, int length) { char* char_buffer = (char*) buffer; int bytes_parsed = 0; /* Do not exceed maximum number of elements */ - if (instr->__elementc == GUAC_INSTRUCTION_MAX_ELEMENTS - && instr->state != GUAC_INSTRUCTION_PARSE_COMPLETE) { - instr->state = GUAC_INSTRUCTION_PARSE_ERROR; + if (parser->__elementc == GUAC_INSTRUCTION_MAX_ELEMENTS + && parser->state != GUAC_PARSE_COMPLETE) { + parser->state = GUAC_PARSE_ERROR; return 0; } /* Parse element length */ - if (instr->state == GUAC_INSTRUCTION_PARSE_LENGTH) { + if (parser->state == GUAC_PARSE_LENGTH) { - int parsed_length = instr->__element_length; + int parsed_length = parser->__element_length; while (bytes_parsed < length) { /* Pull next character */ @@ -83,14 +86,14 @@ int guac_instruction_append(guac_instruction* instr, /* If period, switch to parsing content */ else if (c == '.') { - instr->__elementv[instr->__elementc++] = char_buffer; - instr->state = GUAC_INSTRUCTION_PARSE_CONTENT; + parser->__elementv[parser->__elementc++] = char_buffer; + parser->state = GUAC_PARSE_CONTENT; break; } /* If not digit, parse error */ else { - instr->state = GUAC_INSTRUCTION_PARSE_ERROR; + parser->state = GUAC_PARSE_ERROR; return 0; } @@ -98,19 +101,19 @@ int guac_instruction_append(guac_instruction* instr, /* If too long, parse error */ if (parsed_length > GUAC_INSTRUCTION_MAX_LENGTH) { - instr->state = GUAC_INSTRUCTION_PARSE_ERROR; + parser->state = GUAC_PARSE_ERROR; return 0; } /* Save length */ - instr->__element_length = parsed_length; + parser->__element_length = parsed_length; } /* end parse length */ /* Parse element content */ - if (instr->state == GUAC_INSTRUCTION_PARSE_CONTENT) { + if (parser->state == GUAC_PARSE_CONTENT) { - while (bytes_parsed < length && instr->__element_length >= 0) { + while (bytes_parsed < length && parser->__element_length >= 0) { /* Get length of current character */ char c = *char_buffer; @@ -124,35 +127,35 @@ int guac_instruction_append(guac_instruction* instr, bytes_parsed += char_length; /* If end of element, handle terminator */ - if (instr->__element_length == 0) { + if (parser->__element_length == 0) { *char_buffer = '\0'; /* If semicolon, store end-of-instruction */ if (c == ';') { - instr->state = GUAC_INSTRUCTION_PARSE_COMPLETE; - instr->opcode = instr->__elementv[0]; - instr->argv = &(instr->__elementv[1]); - instr->argc = instr->__elementc - 1; + parser->state = GUAC_PARSE_COMPLETE; + parser->opcode = parser->__elementv[0]; + parser->argv = &(parser->__elementv[1]); + parser->argc = parser->__elementc - 1; break; } /* If comma, move on to next element */ else if (c == ',') { - instr->state = GUAC_INSTRUCTION_PARSE_LENGTH; + parser->state = GUAC_PARSE_LENGTH; break; } /* Otherwise, parse error */ else { - instr->state = GUAC_INSTRUCTION_PARSE_ERROR; + parser->state = GUAC_PARSE_ERROR; return 0; } } /* end if end of element */ /* Advance to next character */ - instr->__element_length--; + parser->__element_length--; char_buffer += char_length; } @@ -163,27 +166,25 @@ int guac_instruction_append(guac_instruction* instr, } -/* Returns new instruction if one exists, or NULL if no more instructions. */ -guac_instruction* guac_instruction_read(guac_socket* socket, - int usec_timeout) { +int guac_parser_read(guac_parser* parser, guac_socket* socket, int usec_timeout) { - char* unparsed_end = socket->__instructionbuf_unparsed_end; - char* unparsed_start = socket->__instructionbuf_unparsed_start; - char* instr_start = socket->__instructionbuf_unparsed_start; - char* buffer_end = socket->__instructionbuf - + sizeof(socket->__instructionbuf); + char* unparsed_end = parser->__instructionbuf_unparsed_end; + char* unparsed_start = parser->__instructionbuf_unparsed_start; + char* instr_start = parser->__instructionbuf_unparsed_start; + char* buffer_end = parser->__instructionbuf + sizeof(parser->__instructionbuf); - guac_instruction* instruction = guac_instruction_alloc(); + /* Begin next instruction if previous was ended */ + if (parser->state == GUAC_PARSE_COMPLETE) + guac_parser_reset(parser); - while (instruction->state != GUAC_INSTRUCTION_PARSE_COMPLETE - && instruction->state != GUAC_INSTRUCTION_PARSE_ERROR) { + while (parser->state != GUAC_PARSE_COMPLETE + && parser->state != GUAC_PARSE_ERROR) { /* Add any available data to buffer */ - int parsed = guac_instruction_append(instruction, unparsed_start, - unparsed_end - unparsed_start); + int parsed = guac_parser_append(parser, unparsed_start, unparsed_end - unparsed_start); /* Read more data if not enough data to parse */ - if (parsed == 0) { + if (parsed == 0 && parser->state != GUAC_PARSE_ERROR) { int retval; @@ -191,23 +192,23 @@ guac_instruction* guac_instruction_read(guac_socket* socket, if (unparsed_end == buffer_end) { /* Shift backward if possible */ - if (instr_start != socket->__instructionbuf) { + if (instr_start != parser->__instructionbuf) { int i; /* Shift buffer */ - int offset = instr_start - socket->__instructionbuf; - memmove(socket->__instructionbuf, instr_start, + int offset = instr_start - parser->__instructionbuf; + memmove(parser->__instructionbuf, instr_start, unparsed_end - instr_start); /* Update tracking pointers */ unparsed_end -= offset; unparsed_start -= offset; - instr_start = socket->__instructionbuf; + instr_start = parser->__instructionbuf; /* Update parsed elements, if any */ - for (i=0; i__elementc; i++) - instruction->__elementv[i] -= offset; + for (i=0; i < parser->__elementc; i++) + parser->__elementv[i] -= offset; } @@ -215,7 +216,7 @@ guac_instruction* guac_instruction_read(guac_socket* socket, else { guac_error = GUAC_STATUS_NO_MEMORY; guac_error_message = "Instruction too long"; - return NULL; + return -1; } } @@ -223,7 +224,7 @@ guac_instruction* guac_instruction_read(guac_socket* socket, /* No instruction yet? Get more data ... */ retval = guac_socket_select(socket, usec_timeout); if (retval <= 0) - return NULL; + return -1; /* Attempt to fill buffer */ retval = guac_socket_read(socket, unparsed_end, @@ -233,7 +234,7 @@ guac_instruction* guac_instruction_read(guac_socket* socket, if (retval < 0) { guac_error = GUAC_STATUS_SEE_ERRNO; guac_error_message = "Error filling instruction buffer"; - return NULL; + return -1; } /* EOF */ @@ -241,7 +242,7 @@ guac_instruction* guac_instruction_read(guac_socket* socket, guac_error = GUAC_STATUS_CLOSED; guac_error_message = "End of stream reached while " "reading instruction"; - return NULL; + return -1; } /* Update internal buffer */ @@ -256,55 +257,65 @@ guac_instruction* guac_instruction_read(guac_socket* socket, } /* end while parsing data */ /* Fail on error */ - if (instruction->state == GUAC_INSTRUCTION_PARSE_ERROR) { + if (parser->state == GUAC_PARSE_ERROR) { guac_error = GUAC_STATUS_PROTOCOL_ERROR; guac_error_message = "Instruction parse error"; - return NULL; + return -1; } - socket->__instructionbuf_unparsed_start = unparsed_start; - socket->__instructionbuf_unparsed_end = unparsed_end; - return instruction; + parser->__instructionbuf_unparsed_start = unparsed_start; + parser->__instructionbuf_unparsed_end = unparsed_end; + return 0; } -guac_instruction* guac_instruction_expect(guac_socket* socket, int usec_timeout, - const char* opcode) { +int guac_parser_expect(guac_parser* parser, guac_socket* socket, int usec_timeout, const char* opcode) { - guac_instruction* instruction; - - /* Wait for data until timeout */ - if (guac_instruction_waiting(socket, usec_timeout) <= 0) - return NULL; - - /* Read available instruction */ - instruction = guac_instruction_read(socket, usec_timeout); - if (instruction == NULL) - return NULL; + /* Read next instruction */ + if (guac_parser_read(parser, socket, usec_timeout) != 0) + return -1; /* Validate instruction */ - if (strcmp(instruction->opcode, opcode) != 0) { + if (strcmp(parser->opcode, opcode) != 0) { guac_error = GUAC_STATUS_PROTOCOL_ERROR; guac_error_message = "Instruction read did not have expected opcode"; - guac_instruction_free(instruction); - return NULL; + return -1; } - /* Return instruction if valid */ - return instruction; + /* Return non-zero only if valid instruction read */ + return parser->state != GUAC_PARSE_COMPLETE; } -void guac_instruction_free(guac_instruction* instruction) { - free(instruction); +int guac_parser_length(guac_parser* parser) { + + char* unparsed_end = parser->__instructionbuf_unparsed_end; + char* unparsed_start = parser->__instructionbuf_unparsed_start; + + return unparsed_end - unparsed_start; + } -int guac_instruction_waiting(guac_socket* socket, int usec_timeout) { +int guac_parser_shift(guac_parser* parser, void* buffer, int length) { - if (socket->__instructionbuf_unparsed_end > - socket->__instructionbuf_unparsed_start) - return 1; + char* copy_end = parser->__instructionbuf_unparsed_end; + char* copy_start = parser->__instructionbuf_unparsed_start; + + /* Contain copy region within length */ + if (copy_end - copy_start > length) + copy_end = copy_start + length; + + /* Copy buffer */ + length = copy_end - copy_start; + memcpy(buffer, copy_start, length); + + parser->__instructionbuf_unparsed_start = copy_end; + + return length; - return guac_socket_select(socket, usec_timeout); +} + +void guac_parser_free(guac_parser* parser) { + free(parser); } diff --git a/src/libguac/plugin.c b/src/libguac/plugin.c deleted file mode 100644 index 96845d48..00000000 --- a/src/libguac/plugin.c +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "config.h" - -#include "client.h" -#include "error.h" -#include "plugin.h" - -#include -#include -#include -#include - -guac_client_plugin* guac_client_plugin_open(const char* protocol) { - - guac_client_plugin* plugin; - - /* Reference to dlopen()'d plugin */ - void* client_plugin_handle; - - /* Client args description */ - const char** client_args; - - /* Pluggable client */ - char protocol_lib[GUAC_PROTOCOL_LIBRARY_LIMIT] = - GUAC_PROTOCOL_LIBRARY_PREFIX; - - union { - guac_client_init_handler* client_init; - void* obj; - } alias; - - /* Add protocol and .so suffix to protocol_lib */ - strncat(protocol_lib, protocol, GUAC_PROTOCOL_NAME_LIMIT-1); - strcat(protocol_lib, GUAC_PROTOCOL_LIBRARY_SUFFIX); - - /* Load client plugin */ - client_plugin_handle = dlopen(protocol_lib, RTLD_LAZY); - if (!client_plugin_handle) { - guac_error = GUAC_STATUS_NOT_FOUND; - guac_error_message = dlerror(); - return NULL; - } - - dlerror(); /* Clear errors */ - - /* Get init function */ - alias.obj = dlsym(client_plugin_handle, "guac_client_init"); - - /* Fail if cannot find guac_client_init */ - if (dlerror() != NULL) { - guac_error = GUAC_STATUS_INTERNAL_ERROR; - guac_error_message = dlerror(); - return NULL; - } - - /* Get usage strig */ - client_args = (const char**) dlsym(client_plugin_handle, "GUAC_CLIENT_ARGS"); - - /* Fail if cannot find GUAC_CLIENT_ARGS */ - if (dlerror() != NULL) { - guac_error = GUAC_STATUS_INTERNAL_ERROR; - guac_error_message = dlerror(); - return NULL; - } - - /* Allocate plugin */ - plugin = malloc(sizeof(guac_client_plugin)); - if (plugin == NULL) { - guac_error = GUAC_STATUS_NO_MEMORY; - guac_error_message = "Could not allocate memory for client plugin"; - return NULL; - } - - /* Init and return plugin */ - plugin->__client_plugin_handle = client_plugin_handle; - plugin->init_handler = alias.client_init; - plugin->args = client_args; - return plugin; - -} - -int guac_client_plugin_close(guac_client_plugin* plugin) { - - /* Unload client plugin */ - if (dlclose(plugin->__client_plugin_handle)) { - guac_error = GUAC_STATUS_INTERNAL_ERROR; - guac_error_message = dlerror(); - return -1; - } - - /* Free plugin handle */ - free(plugin); - return 0; - -} - -int guac_client_plugin_init_client(guac_client_plugin* plugin, - guac_client* client, int argc, char** argv) { - - return plugin->init_handler(client, argc, argv); - -} - diff --git a/src/libguac/pool.c b/src/libguac/pool.c index 37984e5a..cedf8ac9 100644 --- a/src/libguac/pool.c +++ b/src/libguac/pool.c @@ -28,6 +28,7 @@ guac_pool* guac_pool_alloc(int size) { + pthread_mutexattr_t lock_attributes; guac_pool* pool = malloc(sizeof(guac_pool)); /* If unable to allocate, just return NULL. */ @@ -41,6 +42,11 @@ guac_pool* guac_pool_alloc(int size) { pool->__head = NULL; pool->__tail = NULL; + /* Init lock */ + pthread_mutexattr_init(&lock_attributes); + pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&(pool->__lock), &lock_attributes); + return pool; } @@ -57,6 +63,9 @@ void guac_pool_free(guac_pool* pool) { free(old); } + /* Destroy lock */ + pthread_mutex_destroy(&(pool->__lock)); + /* Free pool */ free(pool); @@ -66,11 +75,17 @@ int guac_pool_next_int(guac_pool* pool) { int value; + /* Acquire exclusive access */ + pthread_mutex_lock(&(pool->__lock)); + pool->active++; /* If more integers are needed, return a new one. */ - if (pool->__head == NULL || pool->__next_value < pool->min_size) - return pool->__next_value++; + if (pool->__head == NULL || pool->__next_value < pool->min_size) { + value = pool->__next_value++; + pthread_mutex_unlock(&(pool->__lock)); + return value; + } /* Otherwise, remove first integer. */ value = pool->__head->value; @@ -90,6 +105,7 @@ int guac_pool_next_int(guac_pool* pool) { } /* Return retrieved value. */ + pthread_mutex_unlock(&(pool->__lock)); return value; } @@ -100,6 +116,9 @@ void guac_pool_free_int(guac_pool* pool, int value) { pool_int->value = value; pool_int->__next = NULL; + /* Acquire exclusive access */ + pthread_mutex_lock(&(pool->__lock)); + pool->active--; /* If pool empty, store as sole entry. */ @@ -112,5 +131,8 @@ void guac_pool_free_int(guac_pool* pool, int value) { pool->__tail = pool_int; } + /* Value has been freed */ + pthread_mutex_unlock(&(pool->__lock)); + } diff --git a/src/libguac/raw_encoder.c b/src/libguac/raw_encoder.c index 91efdc0f..5de3ad1d 100644 --- a/src/libguac/raw_encoder.c +++ b/src/libguac/raw_encoder.c @@ -29,14 +29,15 @@ #include #include #include +#include #include #include #include -static void raw_encoder_begin_handler(guac_audio_stream* audio) { +static void raw_encoder_send_audio(guac_audio_stream* audio, + guac_socket* socket) { - raw_encoder_state* state; char mimetype[256]; /* Produce mimetype string from format info */ @@ -44,7 +45,16 @@ static void raw_encoder_begin_handler(guac_audio_stream* audio) { audio->bps, audio->rate, audio->channels); /* Associate stream */ - guac_protocol_send_audio(audio->client->socket, audio->stream, mimetype); + guac_protocol_send_audio(socket, audio->stream, mimetype); + +} + +static void raw_encoder_begin_handler(guac_audio_stream* audio) { + + raw_encoder_state* state; + + /* Broadcast existence of stream */ + raw_encoder_send_audio(audio, audio->client->socket); /* Allocate and init encoder state */ audio->data = state = malloc(sizeof(raw_encoder_state)); @@ -55,9 +65,13 @@ static void raw_encoder_begin_handler(guac_audio_stream* audio) { state->buffer = malloc(state->length); - guac_client_log(audio->client, GUAC_LOG_DEBUG, - "Using raw encoder (%s) with a %i byte buffer.", - mimetype, state->length); +} + +static void raw_encoder_join_handler(guac_audio_stream* audio, + guac_user* user) { + + /* Notify user of existence of stream */ + raw_encoder_send_audio(audio, user->socket); } @@ -143,6 +157,7 @@ guac_audio_encoder _raw8_encoder = { .begin_handler = raw_encoder_begin_handler, .write_handler = raw_encoder_write_handler, .flush_handler = raw_encoder_flush_handler, + .join_handler = raw_encoder_join_handler, .end_handler = raw_encoder_end_handler }; @@ -152,6 +167,7 @@ guac_audio_encoder _raw16_encoder = { .begin_handler = raw_encoder_begin_handler, .write_handler = raw_encoder_write_handler, .flush_handler = raw_encoder_flush_handler, + .join_handler = raw_encoder_join_handler, .end_handler = raw_encoder_end_handler }; diff --git a/src/libguac/socket-fd.c b/src/libguac/socket-fd.c index 5bd9bf34..b137e38f 100644 --- a/src/libguac/socket-fd.c +++ b/src/libguac/socket-fd.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,9 +25,11 @@ #include "error.h" #include "socket.h" +#include #include #include #include +#include #include #include @@ -37,16 +39,116 @@ #include #endif -typedef struct __guac_socket_fd_data { +/** + * Data associated with an open socket which writes to a file descriptor. + */ +typedef struct guac_socket_fd_data { + /** + * The associated file descriptor; + */ int fd; -} __guac_socket_fd_data; + /** + * The number of bytes currently in the main write buffer. + */ + int written; -ssize_t __guac_socket_fd_read_handler(guac_socket* socket, + /** + * The main write buffer. Bytes written go here before being flushed + * to the open file descriptor. + */ + char out_buf[GUAC_SOCKET_OUTPUT_BUFFER_SIZE]; + + /** + * Lock which is acquired when an instruction is being written, and + * released when the instruction is finished being written. + */ + pthread_mutex_t socket_lock; + + /** + * Lock which protects access to the internal buffer of this socket, + * guaranteeing atomicity of writes and flushes. + */ + pthread_mutex_t buffer_lock; + +} guac_socket_fd_data; + +/** + * Writes the entire contents of the given buffer to the file descriptor + * associated with the given socket, retrying as necessary until the whole + * buffer is written, and aborting if an error occurs. + * + * @param socket + * The guac_socket associated with the file descriptor to which the given + * buffer should be written. + * + * @param buf + * The buffer of data to write to the given guac_socket. + * + * @param count + * The number of bytes within the given buffer. + * + * @return + * The number of bytes written, which will be exactly the size of the given + * buffer, or a negative value if an error occurs. + */ +ssize_t guac_socket_fd_write(guac_socket* socket, + const void* buf, size_t count) { + + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; + const char* buffer = buf; + + /* Write until completely written */ + while (count > 0) { + + int retval; + +#ifdef __MINGW32__ + /* MINGW32 WINSOCK only works with send() */ + retval = send(data->fd, buf, count, 0); +#else + /* Use write() for all other platforms */ + retval = write(data->fd, buf, count); +#endif + + /* Record errors in guac_error */ + if (retval < 0) { + guac_error = GUAC_STATUS_SEE_ERRNO; + guac_error_message = "Error writing data to socket"; + return retval; + } + + /* Advance buffer as data retval */ + buffer += retval; + count -= retval; + + } + + return 0; + +} + +/** + * Attempts to read from the underlying file descriptor of the given + * guac_socket, populating the given buffer. + * + * @param socket + * The guac_socket being read from. + * + * @param buf + * The arbitrary buffer which we must populate with data. + * + * @param count + * The maximum number of bytes to read into the buffer. + * + * @return + * The number of bytes read, or -1 if an error occurs. + */ +static ssize_t guac_socket_fd_read_handler(guac_socket* socket, void* buf, size_t count) { - __guac_socket_fd_data* data = (__guac_socket_fd_data*) socket->data; + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; /* Read from socket */ int retval = read(data->fd, buf, count); @@ -61,32 +163,182 @@ ssize_t __guac_socket_fd_read_handler(guac_socket* socket, } -ssize_t __guac_socket_fd_write_handler(guac_socket* socket, - const void* buf, size_t count) { +/** + * Flushes the contents of the output buffer of the given socket immediately, + * without first locking access to the output buffer. This function must ONLY + * be called if the buffer lock has already been acquired. + * + * @param socket + * The guac_socket to flush. + * + * @return + * Zero if the flush operation was successful, non-zero otherwise. + */ +static ssize_t guac_socket_fd_flush(guac_socket* socket) { - __guac_socket_fd_data* data = (__guac_socket_fd_data*) socket->data; - int retval; + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; -#ifdef __MINGW32__ - /* MINGW32 WINSOCK only works with send() */ - retval = send(data->fd, buf, count, 0); -#else - /* Use write() for all other platforms */ - retval = write(data->fd, buf, count); -#endif + /* Flush remaining bytes in buffer */ + if (data->written > 0) { - /* Record errors in guac_error */ - if (retval < 0) { - guac_error = GUAC_STATUS_SEE_ERRNO; - guac_error_message = "Error writing data to socket"; + /* Write ALL bytes in buffer immediately */ + if (guac_socket_fd_write(socket, data->out_buf, data->written)) + return 1; + + data->written = 0; } - return retval; + return 0; + } -int __guac_socket_fd_select_handler(guac_socket* socket, int usec_timeout) { +/** + * Flushes the internal buffer of the given guac_socket, writing all data + * to the underlying file descriptor. + * + * @param socket + * The guac_socket to flush. + * + * @return + * Zero if the flush operation was successful, non-zero otherwise. + */ +static ssize_t guac_socket_fd_flush_handler(guac_socket* socket) { - __guac_socket_fd_data* data = (__guac_socket_fd_data*) socket->data; + int retval; + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; + + /* Acquire exclusive access to buffer */ + pthread_mutex_lock(&(data->buffer_lock)); + + /* Flush contents of buffer */ + retval = guac_socket_fd_flush(socket); + + /* Relinquish exclusive access to buffer */ + pthread_mutex_unlock(&(data->buffer_lock)); + + return retval; + +} + +/** + * Writes the contents of the buffer to the output buffer of the given socket, + * flushing the output buffer as necessary, without first locking access to the + * output buffer. This function must ONLY be called if the buffer lock has + * already been acquired. + * + * @param socket + * The guac_socket to write the given buffer to. + * + * @param buf + * The buffer to write to the given socket. + * + * @param count + * The number of bytes in the given buffer. + * + * @return + * The number of bytes written, or a negative value if an error occurs + * during write. + */ +static ssize_t guac_socket_fd_write_buffered(guac_socket* socket, + const void* buf, size_t count) { + + size_t original_count = count; + const char* current = buf; + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; + + /* Append to buffer, flush if necessary */ + while (count > 0) { + + int chunk_size; + int remaining = sizeof(data->out_buf) - data->written; + + /* If no space left in buffer, flush and retry */ + if (remaining == 0) { + + /* Abort if error occurs during flush */ + if (guac_socket_fd_flush(socket)) + return -1; + + /* Retry buffer append */ + continue; + + } + + /* Calculate size of chunk to be written to buffer */ + chunk_size = count; + if (chunk_size > remaining) + chunk_size = remaining; + + /* Update output buffer */ + memcpy(data->out_buf + data->written, current, chunk_size); + data->written += chunk_size; + + /* Update provided buffer */ + current += chunk_size; + count -= chunk_size; + + } + + /* All bytes have been written, possibly some to the internal buffer */ + return original_count; + +} + +/** + * Appends the provided data to the internal buffer for future writing. The + * actual write attempt will occur only upon flush, or when the internal buffer + * is full. + * + * @param socket + * The guac_socket being write to. + * + * @param buf + * The arbitrary buffer containing the data to be written. + * + * @param count + * The number of bytes contained within the buffer. + * + * @return + * The number of bytes written, or -1 if an error occurs. + */ +static ssize_t guac_socket_fd_write_handler(guac_socket* socket, + const void* buf, size_t count) { + + int retval; + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; + + /* Acquire exclusive access to buffer */ + pthread_mutex_lock(&(data->buffer_lock)); + + /* Write provided data to buffer */ + retval = guac_socket_fd_write_buffered(socket, buf, count); + + /* Relinquish exclusive access to buffer */ + pthread_mutex_unlock(&(data->buffer_lock)); + + return retval; + +} + +/** + * Waits for data on the underlying file desriptor of the given socket to + * become available such that the next read operation will not block. + * + * @param socket + * The guac_socket to wait for. + * + * @param usec_timeout + * The maximum amount of time to wait for data, in microseconds, or -1 to + * potentially wait forever. + * + * @return + * A positive value on success, zero if the timeout elapsed and no data is + * available, or a negative value if an error occurs. + */ +static int guac_socket_fd_select_handler(guac_socket* socket, + int usec_timeout) { + + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; fd_set fds; struct timeval timeout; @@ -122,20 +374,91 @@ int __guac_socket_fd_select_handler(guac_socket* socket, int usec_timeout) { } +/** + * Frees all implementation-specific data associated with the given socket, but + * not the socket object itself. + * + * @param socket + * The guac_socket whose associated data should be freed. + * + * @return + * Zero if the data was successfully freed, non-zero otherwise. This + * implementation always succeeds, and will always return zero. + */ +static int guac_socket_fd_free_handler(guac_socket* socket) { + + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; + + /* Destroy locks */ + pthread_mutex_destroy(&(data->socket_lock)); + pthread_mutex_destroy(&(data->buffer_lock)); + + /* Close file descriptor */ + close(data->fd); + + free(data); + return 0; + +} + +/** + * Acquires exclusive access to the given socket. + * + * @param socket + * The guac_socket to which exclusive access is required. + */ +static void guac_socket_fd_lock_handler(guac_socket* socket) { + + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; + + /* Acquire exclusive access to socket */ + pthread_mutex_lock(&(data->socket_lock)); + +} + +/** + * Relinquishes exclusive access to the given socket. + * + * @param socket + * The guac_socket to which exclusive access is no longer required. + */ +static void guac_socket_fd_unlock_handler(guac_socket* socket) { + + guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; + + /* Relinquish exclusive access to socket */ + pthread_mutex_unlock(&(data->socket_lock)); + +} + guac_socket* guac_socket_open(int fd) { + pthread_mutexattr_t lock_attributes; + /* Allocate socket and associated data */ guac_socket* socket = guac_socket_alloc(); - __guac_socket_fd_data* data = malloc(sizeof(__guac_socket_fd_data)); + guac_socket_fd_data* data = malloc(sizeof(guac_socket_fd_data)); /* Store file descriptor as socket data */ data->fd = fd; + data->written = 0; socket->data = data; + pthread_mutexattr_init(&lock_attributes); + pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); + + /* Init locks */ + pthread_mutex_init(&(data->socket_lock), &lock_attributes); + pthread_mutex_init(&(data->buffer_lock), &lock_attributes); + /* Set read/write handlers */ - socket->read_handler = __guac_socket_fd_read_handler; - socket->write_handler = __guac_socket_fd_write_handler; - socket->select_handler = __guac_socket_fd_select_handler; + socket->read_handler = guac_socket_fd_read_handler; + socket->write_handler = guac_socket_fd_write_handler; + socket->select_handler = guac_socket_fd_select_handler; + socket->lock_handler = guac_socket_fd_lock_handler; + socket->unlock_handler = guac_socket_fd_unlock_handler; + socket->flush_handler = guac_socket_fd_flush_handler; + socket->free_handler = guac_socket_fd_free_handler; return socket; diff --git a/src/libguac/socket-nest.c b/src/libguac/socket-nest.c index 3b3b313f..8ce57672 100644 --- a/src/libguac/socket-nest.c +++ b/src/libguac/socket-nest.c @@ -105,6 +105,27 @@ ssize_t __guac_socket_nest_write_handler(guac_socket* socket, } +/** + * Frees all implementation-specific data associated with the given socket, but + * not the socket object itself. + * + * @param socket + * The guac_socket whose associated data should be freed. + * + * @return + * Zero if the data was successfully freed, non-zero otherwise. This + * implementation always succeeds, and will always return zero. + */ +static int __guac_socket_nest_free_handler(guac_socket* socket) { + + /* Free associated data */ + __guac_socket_nest_data* data = (__guac_socket_nest_data*) socket->data; + free(data); + + return 0; + +} + guac_socket* guac_socket_nest(guac_socket* parent, int index) { /* Allocate socket and associated data */ @@ -115,8 +136,9 @@ guac_socket* guac_socket_nest(guac_socket* parent, int index) { data->parent = parent; socket->data = data; - /* Set write handler */ + /* Set write and free handlers */ socket->write_handler = __guac_socket_nest_write_handler; + socket->free_handler = __guac_socket_nest_free_handler; return socket; diff --git a/src/libguac/socket.c b/src/libguac/socket.c index e0786790..f9c33289 100644 --- a/src/libguac/socket.c +++ b/src/libguac/socket.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -138,7 +139,6 @@ int guac_socket_select(guac_socket* socket, int usec_timeout) { guac_socket* guac_socket_alloc() { - pthread_mutexattr_t lock_attributes; guac_socket* socket = malloc(sizeof(guac_socket)); /* If no memory available, return with error */ @@ -149,46 +149,28 @@ guac_socket* guac_socket_alloc() { } socket->__ready = 0; - socket->__written = 0; socket->data = NULL; socket->state = GUAC_SOCKET_OPEN; socket->last_write_timestamp = guac_timestamp_current(); - /* Init members */ - socket->__instructionbuf_unparsed_start = socket->__instructionbuf; - socket->__instructionbuf_unparsed_end = socket->__instructionbuf; - - /* Default to unsafe threading */ - socket->__threadsafe_instructions = 0; - /* No keep alive ping by default */ socket->__keep_alive_enabled = 0; - pthread_mutexattr_init(&lock_attributes); - pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); - - pthread_mutex_init(&(socket->__instruction_write_lock), &lock_attributes); - pthread_mutex_init(&(socket->__buffer_lock), &lock_attributes); - /* No handlers yet */ socket->read_handler = NULL; socket->write_handler = NULL; socket->select_handler = NULL; socket->free_handler = NULL; + socket->flush_handler = NULL; + socket->lock_handler = NULL; + socket->unlock_handler = NULL; return socket; } -void guac_socket_require_threadsafe(guac_socket* socket) { - socket->__threadsafe_instructions = 1; -} - void guac_socket_require_keep_alive(guac_socket* socket) { - /* Keep-alive thread requires a threadsafe socket */ - guac_socket_require_threadsafe(socket); - /* Start keep-alive thread */ socket->__keep_alive_enabled = 1; pthread_create(&(socket->__keep_alive_thread), NULL, @@ -198,44 +180,28 @@ void guac_socket_require_keep_alive(guac_socket* socket) { void guac_socket_instruction_begin(guac_socket* socket) { - /* Lock writes if threadsafety enabled */ - if (socket->__threadsafe_instructions) - pthread_mutex_lock(&(socket->__instruction_write_lock)); + /* Call instruction begin handler if defined */ + if (socket->lock_handler) + socket->lock_handler(socket); } void guac_socket_instruction_end(guac_socket* socket) { - /* Unlock writes if threadsafety enabled */ - if (socket->__threadsafe_instructions) - pthread_mutex_unlock(&(socket->__instruction_write_lock)); - -} - -void guac_socket_update_buffer_begin(guac_socket* socket) { - - /* Lock if threadsafety enabled */ - if (socket->__threadsafe_instructions) - pthread_mutex_lock(&(socket->__buffer_lock)); - -} - -void guac_socket_update_buffer_end(guac_socket* socket) { - - /* Unlock if threadsafety enabled */ - if (socket->__threadsafe_instructions) - pthread_mutex_unlock(&(socket->__buffer_lock)); + /* Call instruction end handler if defined */ + if (socket->unlock_handler) + socket->unlock_handler(socket); } void guac_socket_free(guac_socket* socket) { + guac_socket_flush(socket); + /* Call free handler if defined */ if (socket->free_handler) socket->free_handler(socket); - guac_socket_flush(socket); - /* Mark as closed */ socket->state = GUAC_SOCKET_CLOSED; @@ -243,91 +209,92 @@ void guac_socket_free(guac_socket* socket) { if (socket->__keep_alive_enabled) pthread_join(socket->__keep_alive_thread, NULL); - pthread_mutex_destroy(&(socket->__instruction_write_lock)); free(socket); } ssize_t guac_socket_write_int(guac_socket* socket, int64_t i) { char buffer[128]; - snprintf(buffer, sizeof(buffer), "%"PRIi64, i); - return guac_socket_write_string(socket, buffer); + int length; + + /* Write provided integer as a string */ + length = snprintf(buffer, sizeof(buffer), "%"PRIi64, i); + return guac_socket_write(socket, buffer, length); } ssize_t guac_socket_write_string(guac_socket* socket, const char* str) { - char* __out_buf = socket->__out_buf; + /* Write contents of string */ + if (guac_socket_write(socket, str, strlen(str))) + return 1; - guac_socket_update_buffer_begin(socket); - - for (; *str != '\0'; str++) { - - __out_buf[socket->__written++] = *str; - - /* Flush when necessary, return on error. Note that we must flush within 4 bytes of boundary because - * __guac_socket_write_base64_triplet ALWAYS writes four bytes, and would otherwise potentially overflow - * the buffer. */ - if (socket->__written > GUAC_SOCKET_OUTPUT_BUFFER_SIZE - 4) { - - if (guac_socket_write(socket, __out_buf, socket->__written)) { - guac_socket_update_buffer_end(socket); - return 1; - } - - socket->__written = 0; - - } - - } - - guac_socket_update_buffer_end(socket); return 0; } -ssize_t __guac_socket_write_base64_triplet(guac_socket* socket, int a, int b, int c) { +ssize_t __guac_socket_write_base64_triplet(guac_socket* socket, + int a, int b, int c) { - char* __out_buf = socket->__out_buf; + char output[4]; - /* Byte 1 */ - __out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[(a & 0xFC) >> 2]; /* [AAAAAA]AABBBB BBBBCC CCCCCC */ + /* Byte 0:[AAAAAA] AABBBB BBBBCC CCCCCC */ + output[0] = __guac_socket_BASE64_CHARACTERS[(a & 0xFC) >> 2]; if (b >= 0) { - __out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[((a & 0x03) << 4) | ((b & 0xF0) >> 4)]; /* AAAAAA[AABBBB]BBBBCC CCCCCC */ + /* Byte 1: AAAAAA [AABBBB] BBBBCC CCCCCC */ + output[1] = __guac_socket_BASE64_CHARACTERS[((a & 0x03) << 4) | ((b & 0xF0) >> 4)]; + + /* + * Bytes 2 and 3, zero characters of padding: + * + * AAAAAA AABBBB [BBBBCC] CCCCCC + * AAAAAA AABBBB BBBBCC [CCCCCC] + */ if (c >= 0) { - __out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[((b & 0x0F) << 2) | ((c & 0xC0) >> 6)]; /* AAAAAA AABBBB[BBBBCC]CCCCCC */ - __out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[c & 0x3F]; /* AAAAAA AABBBB BBBBCC[CCCCCC] */ + output[2] = __guac_socket_BASE64_CHARACTERS[((b & 0x0F) << 2) | ((c & 0xC0) >> 6)]; + output[3] = __guac_socket_BASE64_CHARACTERS[c & 0x3F]; } + + /* + * Bytes 2 and 3, one character of padding: + * + * AAAAAA AABBBB [BBBB--] ------ + * AAAAAA AABBBB BBBB-- [------] + */ else { - __out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[((b & 0x0F) << 2)]; /* AAAAAA AABBBB[BBBB--]------ */ - __out_buf[socket->__written++] = '='; /* AAAAAA AABBBB BBBB--[------] */ + output[2] = __guac_socket_BASE64_CHARACTERS[((b & 0x0F) << 2)]; + output[3] = '='; } } + + /* + * Bytes 1, 2, and 3, two characters of padding: + * + * AAAAAA [AA----] ------ ------ + * AAAAAA AA---- [------] ------ + * AAAAAA AA---- ------ [------] + */ else { - __out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[((a & 0x03) << 4)]; /* AAAAAA[AA----]------ ------ */ - __out_buf[socket->__written++] = '='; /* AAAAAA AA----[------]------ */ - __out_buf[socket->__written++] = '='; /* AAAAAA AA---- ------[------] */ + output[1] = __guac_socket_BASE64_CHARACTERS[((a & 0x03) << 4)]; + output[2] = '='; + output[3] = '='; } - /* At this point, 4 bytes have been socket->__written */ - - /* Flush when necessary, return on error */ - if (socket->__written > GUAC_SOCKET_OUTPUT_BUFFER_SIZE - 4) { - - if (guac_socket_write(socket, __out_buf, socket->__written)) - return -1; - - socket->__written = 0; - } + /* At this point, 4 base64 bytes have been written */ + if (guac_socket_write(socket, output, 4)) + return -1; + /* If no second byte was provided, only one byte was written */ if (b < 0) return 1; + /* If no third byte was provided, only two bytes were written */ if (c < 0) return 2; + /* Otherwise, three bytes were written */ return 3; } @@ -359,37 +326,25 @@ ssize_t guac_socket_write_base64(guac_socket* socket, const void* buf, size_t co const unsigned char* char_buf = (const unsigned char*) buf; const unsigned char* end = char_buf + count; - guac_socket_update_buffer_begin(socket); while (char_buf < end) { retval = __guac_socket_write_base64_byte(socket, *(char_buf++)); - if (retval < 0) { - guac_socket_update_buffer_end(socket); + if (retval < 0) return retval; - } } - guac_socket_update_buffer_end(socket); return 0; } ssize_t guac_socket_flush(guac_socket* socket) { - /* Flush remaining bytes in buffer */ - guac_socket_update_buffer_begin(socket); - if (socket->__written > 0) { + /* If handler defined, call it. */ + if (socket->flush_handler) + return socket->flush_handler(socket); - if (guac_socket_write(socket, socket->__out_buf, socket->__written)) { - guac_socket_update_buffer_end(socket); - return 1; - } - - socket->__written = 0; - } - - guac_socket_update_buffer_end(socket); + /* Otherwise, do nothing */ return 0; } @@ -399,18 +354,14 @@ ssize_t guac_socket_flush_base64(guac_socket* socket) { int retval; /* Flush triplet to output buffer */ - guac_socket_update_buffer_begin(socket); while (socket->__ready > 0) { retval = __guac_socket_write_base64_byte(socket, -1); - if (retval < 0) { - guac_socket_update_buffer_end(socket); + if (retval < 0) return retval; - } } - guac_socket_update_buffer_end(socket); return 0; } diff --git a/src/libguac/timestamp.c b/src/libguac/timestamp.c index 1e1d9652..96c432e1 100644 --- a/src/libguac/timestamp.c +++ b/src/libguac/timestamp.c @@ -56,3 +56,16 @@ guac_timestamp guac_timestamp_current() { } +void guac_timestamp_msleep(int duration) { + + /* Split milliseconds into equivalent seconds + nanoseconds */ + struct timespec sleep_period = { + .tv_sec = duration / 1000, + .tv_nsec = (duration % 1000) * 1000000 + }; + + /* Sleep for specified interval */ + nanosleep(&sleep_period, NULL); + +} + diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c new file mode 100644 index 00000000..6be8ffd5 --- /dev/null +++ b/src/libguac/user-handlers.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "client.h" +#include "object.h" +#include "protocol.h" +#include "stream.h" +#include "timestamp.h" +#include "user.h" +#include "user-handlers.h" + +#include +#include +#include + +/* Guacamole instruction handler map */ + +__guac_instruction_handler_mapping __guac_instruction_handler_map[] = { + {"sync", __guac_handle_sync}, + {"mouse", __guac_handle_mouse}, + {"key", __guac_handle_key}, + {"clipboard", __guac_handle_clipboard}, + {"disconnect", __guac_handle_disconnect}, + {"size", __guac_handle_size}, + {"file", __guac_handle_file}, + {"pipe", __guac_handle_pipe}, + {"ack", __guac_handle_ack}, + {"blob", __guac_handle_blob}, + {"end", __guac_handle_end}, + {"get", __guac_handle_get}, + {"put", __guac_handle_put}, + {NULL, NULL} +}; + +/** + * Parses a 64-bit integer from the given string. It is assumed that the string + * will contain only decimal digits, with an optional leading minus sign. + * The result of parsing a string which does not conform to this pattern is + * undefined. + * + * @param str + * The string to parse, which must contain only decimal digits and an + * optional leading minus sign. + * + * @return + * The 64-bit integer value represented by the given string. + */ +static int64_t __guac_parse_int(const char* str) { + + int sign = 1; + int64_t num = 0; + + for (; *str != '\0'; str++) { + + if (*str == '-') + sign = -sign; + else + num = num * 10 + (*str - '0'); + + } + + return num * sign; + +} + +/* Guacamole instruction handlers */ + +int __guac_handle_sync(guac_user* user, int argc, char** argv) { + + int frame_duration; + + guac_timestamp current = guac_timestamp_current(); + guac_timestamp timestamp = __guac_parse_int(argv[0]); + + /* Error if timestamp is in future */ + if (timestamp > user->client->last_sent_timestamp) + return -1; + + /* Update stored timestamp */ + user->last_received_timestamp = timestamp; + + /* Calculate length of frame, including network and processing lag */ + frame_duration = current - timestamp; + + /* Update lag statistics if at least one frame has been rendered */ + if (user->last_frame_duration != 0) { + + /* Approximate processing lag by summing the frame duration deltas */ + int processing_lag = user->processing_lag + frame_duration + - user->last_frame_duration; + + /* Adjust back to zero if cumulative error leads to a negative value */ + if (processing_lag < 0) + processing_lag = 0; + + user->processing_lag = processing_lag; + + } + + /* Record duration of frame */ + user->last_frame_duration = frame_duration; + + if (user->sync_handler) + return user->sync_handler(user, timestamp); + return 0; +} + +int __guac_handle_mouse(guac_user* user, int argc, char** argv) { + if (user->mouse_handler) + return user->mouse_handler( + user, + atoi(argv[0]), /* x */ + atoi(argv[1]), /* y */ + atoi(argv[2]) /* mask */ + ); + return 0; +} + +int __guac_handle_key(guac_user* user, int argc, char** argv) { + if (user->key_handler) + return user->key_handler( + user, + atoi(argv[0]), /* keysym */ + atoi(argv[1]) /* pressed */ + ); + return 0; +} + +/** + * Retrieves the existing user-level input stream having the given index. These + * will be streams which were created by the remotely-connected user. If the + * index is invalid or too large, this function will automatically respond with + * an "ack" instruction containing an appropriate error code. + * + * @param user + * The user associated with the stream being retrieved. + * + * @param stream_index + * The index of the stream to retrieve. + * + * @return + * The stream associated with the given user and having the given index, + * or NULL if the index is invalid. + */ +static guac_stream* __get_input_stream(guac_user* user, int stream_index) { + + /* Validate stream index */ + if (stream_index < 0 || stream_index >= GUAC_USER_MAX_STREAMS) { + + guac_stream dummy_stream; + dummy_stream.index = stream_index; + + guac_protocol_send_ack(user->socket, &dummy_stream, + "Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); + return NULL; + } + + return &(user->__input_streams[stream_index]); + +} + +/** + * Retrieves the existing, in-progress (open) user-level input stream having + * the given index. These will be streams which were created by the + * remotely-connected user. If the index is invalid, too large, or the stream + * is closed, this function will automatically respond with an "ack" + * instruction containing an appropriate error code. + * + * @param user + * The user associated with the stream being retrieved. + * + * @param stream_index + * The index of the stream to retrieve. + * + * @return + * The in-progress (open)stream associated with the given user and having + * the given index, or NULL if the index is invalid or the stream is + * closed. + */ +static guac_stream* __get_open_input_stream(guac_user* user, int stream_index) { + + guac_stream* stream = __get_input_stream(user, stream_index); + + /* Fail if no such stream */ + if (stream == NULL) + return NULL; + + /* Validate initialization of stream */ + if (stream->index == GUAC_USER_CLOSED_STREAM_INDEX) { + + guac_stream dummy_stream; + dummy_stream.index = stream_index; + + guac_protocol_send_ack(user->socket, &dummy_stream, + "Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); + return NULL; + } + + return stream; + +} + +/** + * Initializes and returns a new user-level input stream having the given + * index, clearing any values that may have been assigned by a past use of the + * underlying stream object storage. If the stream was already open, it will + * first be closed and its end handlers invoked as if explicitly closed by the + * user. + * + * @param user + * The user associated with the stream being initialized. + * + * @param stream_index + * The index of the stream to initialized. + * + * @return + * A new initialized user-level input stream having the given index, or + * NULL if the index is invalid. + */ +static guac_stream* __init_input_stream(guac_user* user, int stream_index) { + + guac_stream* stream = __get_input_stream(user, stream_index); + + /* Fail if no such stream */ + if (stream == NULL) + return NULL; + + /* Force end of previous stream if open */ + if (stream->index != GUAC_USER_CLOSED_STREAM_INDEX) { + + /* Call stream handler if defined */ + if (stream->end_handler) + stream->end_handler(user, stream); + + /* Fall back to global handler if defined */ + else if (user->end_handler) + user->end_handler(user, stream); + + } + + /* Initialize stream */ + stream->index = stream_index; + stream->data = NULL; + stream->ack_handler = NULL; + stream->blob_handler = NULL; + stream->end_handler = NULL; + + return stream; + +} + +int __guac_handle_clipboard(guac_user* user, int argc, char** argv) { + + /* Pull corresponding stream */ + int stream_index = atoi(argv[0]); + guac_stream* stream = __init_input_stream(user, stream_index); + if (stream == NULL) + return 0; + + /* If supported, call handler */ + if (user->clipboard_handler) + return user->clipboard_handler( + user, + stream, + argv[1] /* mimetype */ + ); + + /* Otherwise, abort */ + guac_protocol_send_ack(user->socket, stream, + "Clipboard unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); + return 0; + +} + +int __guac_handle_size(guac_user* user, int argc, char** argv) { + if (user->size_handler) + return user->size_handler( + user, + atoi(argv[0]), /* width */ + atoi(argv[1]) /* height */ + ); + return 0; +} + +int __guac_handle_file(guac_user* user, int argc, char** argv) { + + /* Pull corresponding stream */ + int stream_index = atoi(argv[0]); + guac_stream* stream = __init_input_stream(user, stream_index); + if (stream == NULL) + return 0; + + /* If supported, call handler */ + if (user->file_handler) + return user->file_handler( + user, + stream, + argv[1], /* mimetype */ + argv[2] /* filename */ + ); + + /* Otherwise, abort */ + guac_protocol_send_ack(user->socket, stream, + "File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); + return 0; +} + +int __guac_handle_pipe(guac_user* user, int argc, char** argv) { + + /* Pull corresponding stream */ + int stream_index = atoi(argv[0]); + guac_stream* stream = __init_input_stream(user, stream_index); + if (stream == NULL) + return 0; + + /* If supported, call handler */ + if (user->pipe_handler) + return user->pipe_handler( + user, + stream, + argv[1], /* mimetype */ + argv[2] /* name */ + ); + + /* Otherwise, abort */ + guac_protocol_send_ack(user->socket, stream, + "Named pipes unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); + return 0; +} + +int __guac_handle_ack(guac_user* user, int argc, char** argv) { + + guac_stream* stream; + + /* Parse stream index */ + int stream_index = atoi(argv[0]); + + /* Ignore indices of client-level streams */ + if (stream_index % 2 != 0) + return 0; + + /* Determine index within user-level array of streams */ + stream_index /= 2; + + /* Validate stream index */ + if (stream_index < 0 || stream_index >= GUAC_USER_MAX_STREAMS) + return 0; + + stream = &(user->__output_streams[stream_index]); + + /* Validate initialization of stream */ + if (stream->index == GUAC_USER_CLOSED_STREAM_INDEX) + return 0; + + /* Call stream handler if defined */ + if (stream->ack_handler) + return stream->ack_handler(user, stream, argv[1], + atoi(argv[2])); + + /* Fall back to global handler if defined */ + if (user->ack_handler) + return user->ack_handler(user, stream, argv[1], + atoi(argv[2])); + + return 0; +} + +int __guac_handle_blob(guac_user* user, int argc, char** argv) { + + int stream_index = atoi(argv[0]); + guac_stream* stream = __get_open_input_stream(user, stream_index); + + /* Fail if no such stream */ + if (stream == NULL) + return 0; + + /* Call stream handler if defined */ + if (stream->blob_handler) { + int length = guac_protocol_decode_base64(argv[1]); + return stream->blob_handler(user, stream, argv[1], + length); + } + + /* Fall back to global handler if defined */ + if (user->blob_handler) { + int length = guac_protocol_decode_base64(argv[1]); + return user->blob_handler(user, stream, argv[1], + length); + } + + guac_protocol_send_ack(user->socket, stream, + "File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); + return 0; +} + +int __guac_handle_end(guac_user* user, int argc, char** argv) { + + int result = 0; + int stream_index = atoi(argv[0]); + guac_stream* stream = __get_open_input_stream(user, stream_index); + + /* Fail if no such stream */ + if (stream == NULL) + return 0; + + /* Call stream handler if defined */ + if (stream->end_handler) + result = stream->end_handler(user, stream); + + /* Fall back to global handler if defined */ + else if (user->end_handler) + result = user->end_handler(user, stream); + + /* Mark stream as closed */ + stream->index = GUAC_USER_CLOSED_STREAM_INDEX; + return result; +} + +int __guac_handle_get(guac_user* user, int argc, char** argv) { + + guac_object* object; + + /* Validate object index */ + int object_index = atoi(argv[0]); + if (object_index < 0 || object_index >= GUAC_USER_MAX_OBJECTS) + return 0; + + object = &(user->__objects[object_index]); + + /* Validate initialization of object */ + if (object->index == GUAC_USER_UNDEFINED_OBJECT_INDEX) + return 0; + + /* Call object handler if defined */ + if (object->get_handler) + return object->get_handler( + user, + object, + argv[1] /* name */ + ); + + /* Fall back to global handler if defined */ + if (user->get_handler) + return user->get_handler( + user, + object, + argv[1] /* name */ + ); + + return 0; +} + +int __guac_handle_put(guac_user* user, int argc, char** argv) { + + guac_object* object; + + /* Validate object index */ + int object_index = atoi(argv[0]); + if (object_index < 0 || object_index >= GUAC_USER_MAX_OBJECTS) + return 0; + + object = &(user->__objects[object_index]); + + /* Validate initialization of object */ + if (object->index == GUAC_USER_UNDEFINED_OBJECT_INDEX) + return 0; + + /* Pull corresponding stream */ + int stream_index = atoi(argv[1]); + guac_stream* stream = __init_input_stream(user, stream_index); + if (stream == NULL) + return 0; + + /* Call object handler if defined */ + if (object->put_handler) + return object->put_handler( + user, + object, + stream, + argv[2], /* mimetype */ + argv[3] /* name */ + ); + + /* Fall back to global handler if defined */ + if (user->put_handler) + return user->put_handler( + user, + object, + stream, + argv[2], /* mimetype */ + argv[3] /* name */ + ); + + /* Otherwise, abort */ + guac_protocol_send_ack(user->socket, stream, + "Object write unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); + return 0; +} + +int __guac_handle_disconnect(guac_user* user, int argc, char** argv) { + guac_user_stop(user); + return 0; +} + diff --git a/src/libguac/client-handlers.h b/src/libguac/user-handlers.h similarity index 70% rename from src/libguac/client-handlers.h rename to src/libguac/user-handlers.h index f72a7383..5bfe86bb 100644 --- a/src/libguac/client-handlers.h +++ b/src/libguac/user-handlers.h @@ -21,26 +21,41 @@ */ -#ifndef _GUAC_CLIENT_HANDLERS__H -#define _GUAC_CLIENT_HANDLERS__H +#ifndef _GUAC_USER_HANDLERS__H +#define _GUAC_USER_HANDLERS__H /** * Provides initial handler functions and a lookup structure for automatically - * handling client instructions. This is used only internally within libguac, - * and is not installed along with the library. + * handling instructions received from each user. This is used only internally + * within libguac, and is not installed along with the library. * - * @file client-handlers.h + * @file user-handlers.h */ #include "config.h" #include "client.h" -#include "instruction.h" +#include "timestamp.h" /** - * Internal handler for Guacamole instructions. + * Internal handler for Guacamole instructions. Instruction handlers will be + * invoked when their corresponding instructions are received. The mapping + * of instruction opcode to handler is defined by the + * __guac_instruction_handler_map array. + * + * @param user + * The user that sent the instruction. + * + * @param argc + * The number of arguments in argv. + * + * @param argv + * The arguments included with the instruction, excluding the opcode. + * + * @return + * Zero if the instruction was successfully handled, non-zero otherwise. */ -typedef int __guac_instruction_handler(guac_client* client, guac_instruction* copied); +typedef int __guac_instruction_handler(guac_user* user, int argc, char** argv); /** * Structure mapping an instruction opcode to an instruction handler. @@ -64,91 +79,92 @@ typedef struct __guac_instruction_handler_mapping { * is received, this handler will be called. Sync instructions are automatically * handled, thus there is no client handler for sync instruction. */ -int __guac_handle_sync(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_sync; /** * Internal initial handler for the mouse instruction. When a mouse instruction * is received, this handler will be called. The client's mouse handler will * be invoked if defined. */ -int __guac_handle_mouse(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_mouse; /** * Internal initial handler for the key instruction. When a key instruction * is received, this handler will be called. The client's key handler will * be invoked if defined. */ -int __guac_handle_key(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_key; /** - * Internal initial handler for the clipboard instruction. When a clipboard instruction - * is received, this handler will be called. The client's clipboard handler will - * be invoked if defined. + * Internal initial handler for the clipboard instruction. When a clipboard + * instruction is received, this handler will be called. The client's clipboard + * handler will be invoked if defined. */ -int __guac_handle_clipboard(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_clipboard; /** * Internal initial handler for the file instruction. When a file instruction * is received, this handler will be called. The client's file handler will * be invoked if defined. */ -int __guac_handle_file(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_file; /** * Internal initial handler for the pipe instruction. When a pipe instruction * is received, this handler will be called. The client's pipe handler will * be invoked if defined. */ -int __guac_handle_pipe(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_pipe; /** * Internal initial handler for the ack instruction. When a ack instruction * is received, this handler will be called. The client's ack handler will * be invoked if defined. */ -int __guac_handle_ack(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_ack; /** * Internal initial handler for the blob instruction. When a blob instruction * is received, this handler will be called. The client's blob handler will * be invoked if defined. */ -int __guac_handle_blob(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_blob; /** * Internal initial handler for the end instruction. When a end instruction * is received, this handler will be called. The client's end handler will * be invoked if defined. */ -int __guac_handle_end(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_end; /** * Internal initial handler for the get instruction. When a get instruction * is received, this handler will be called. The client's get handler will * be invoked if defined. */ -int __guac_handle_get(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_get; /** * Internal initial handler for the put instruction. When a put instruction * is received, this handler will be called. The client's put handler will * be invoked if defined. */ -int __guac_handle_put(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_put; /** * Internal initial handler for the size instruction. When a size instruction * is received, this handler will be called. The client's size handler will * be invoked if defined. */ -int __guac_handle_size(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_size; /** - * Internal initial handler for the disconnect instruction. When a disconnect instruction - * is received, this handler will be called. Disconnect instructions are automatically - * handled, thus there is no client handler for disconnect instruction. + * Internal initial handler for the disconnect instruction. When a disconnect + * instruction is received, this handler will be called. Disconnect + * instructions are automatically handled, thus there is no client handler for + * disconnect instruction. */ -int __guac_handle_disconnect(guac_client* client, guac_instruction* instruction); +__guac_instruction_handler __guac_handle_disconnect; /** * Instruction handler mapping table. This is a NULL-terminated array of diff --git a/src/libguac/user.c b/src/libguac/user.c new file mode 100644 index 00000000..075805d1 --- /dev/null +++ b/src/libguac/user.c @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "client.h" +#include "encode-jpeg.h" +#include "encode-png.h" +#include "encode-webp.h" +#include "id.h" +#include "object.h" +#include "pool.h" +#include "protocol.h" +#include "socket.h" +#include "stream.h" +#include "timestamp.h" +#include "user.h" +#include "user-handlers.h" + +#include +#include +#include +#include + +guac_user* guac_user_alloc() { + + guac_user* user = calloc(1, sizeof(guac_user)); + int i; + + /* Generate ID */ + user->user_id = guac_generate_id(GUAC_USER_ID_PREFIX); + if (user->user_id == NULL) { + free(user); + return NULL; + } + + user->last_received_timestamp = guac_timestamp_current(); + user->last_frame_duration = 0; + user->processing_lag = 0; + user->active = 1; + + /* Allocate stream pool */ + user->__stream_pool = guac_pool_alloc(0); + + /* Initialze streams */ + user->__input_streams = malloc(sizeof(guac_stream) * GUAC_USER_MAX_STREAMS); + user->__output_streams = malloc(sizeof(guac_stream) * GUAC_USER_MAX_STREAMS); + + for (i=0; i__input_streams[i].index = GUAC_USER_CLOSED_STREAM_INDEX; + user->__output_streams[i].index = GUAC_USER_CLOSED_STREAM_INDEX; + } + + /* Allocate object pool */ + user->__object_pool = guac_pool_alloc(0); + + /* Initialize objects */ + user->__objects = malloc(sizeof(guac_object) * GUAC_USER_MAX_OBJECTS); + for (i=0; i__objects[i].index = GUAC_USER_UNDEFINED_OBJECT_INDEX; + + return user; + +} + +void guac_user_free(guac_user* user) { + + /* Free streams */ + free(user->__input_streams); + free(user->__output_streams); + + /* Free stream pool */ + guac_pool_free(user->__stream_pool); + + /* Free objects */ + free(user->__objects); + + /* Free object pool */ + guac_pool_free(user->__object_pool); + + /* Clean up user */ + free(user->user_id); + free(user); + +} + +guac_stream* guac_user_alloc_stream(guac_user* user) { + + guac_stream* allocd_stream; + int stream_index; + + /* Refuse to allocate beyond maximum */ + if (user->__stream_pool->active == GUAC_USER_MAX_STREAMS) + return NULL; + + /* Allocate stream */ + stream_index = guac_pool_next_int(user->__stream_pool); + + /* Initialize stream with even index (odd indices are client-level) */ + allocd_stream = &(user->__output_streams[stream_index]); + allocd_stream->index = stream_index * 2; + allocd_stream->data = NULL; + allocd_stream->ack_handler = NULL; + allocd_stream->blob_handler = NULL; + allocd_stream->end_handler = NULL; + + return allocd_stream; + +} + +void guac_user_free_stream(guac_user* user, guac_stream* stream) { + + /* Release index to pool */ + guac_pool_free_int(user->__stream_pool, stream->index / 2); + + /* Mark stream as closed */ + stream->index = GUAC_USER_CLOSED_STREAM_INDEX; + +} + +guac_object* guac_user_alloc_object(guac_user* user) { + + guac_object* allocd_object; + int object_index; + + /* Refuse to allocate beyond maximum */ + if (user->__object_pool->active == GUAC_USER_MAX_OBJECTS) + return NULL; + + /* Allocate object */ + object_index = guac_pool_next_int(user->__object_pool); + + /* Initialize object */ + allocd_object = &(user->__objects[object_index]); + allocd_object->index = object_index; + allocd_object->data = NULL; + allocd_object->get_handler = NULL; + allocd_object->put_handler = NULL; + + return allocd_object; + +} + +void guac_user_free_object(guac_user* user, guac_object* object) { + + /* Release index to pool */ + guac_pool_free_int(user->__object_pool, object->index); + + /* Mark object as undefined */ + object->index = GUAC_USER_UNDEFINED_OBJECT_INDEX; + +} + +int guac_user_handle_instruction(guac_user* user, const char* opcode, int argc, char** argv) { + + /* For each defined instruction */ + __guac_instruction_handler_mapping* current = __guac_instruction_handler_map; + while (current->opcode != NULL) { + + /* If recognized, call handler */ + if (strcmp(opcode, current->opcode) == 0) + return current->handler(user, argc, argv); + + current++; + } + + /* If unrecognized, ignore */ + return 0; + +} + +void guac_user_stop(guac_user* user) { + user->active = 0; +} + +void vguac_user_abort(guac_user* user, guac_protocol_status status, + const char* format, va_list ap) { + + /* Only relevant if user is active */ + if (user->active) { + + /* Log detail of error */ + vguac_user_log(user, GUAC_LOG_ERROR, format, ap); + + /* Send error immediately, limit information given */ + guac_protocol_send_error(user->socket, "Aborted. See logs.", status); + guac_socket_flush(user->socket); + + /* Stop user */ + guac_user_stop(user); + + } + +} + +void guac_user_abort(guac_user* user, guac_protocol_status status, + const char* format, ...) { + + va_list args; + va_start(args, format); + + vguac_user_abort(user, status, format, args); + + va_end(args); + +} + +void vguac_user_log(guac_user* user, guac_client_log_level level, + const char* format, va_list ap) { + + vguac_client_log(user->client, level, format, ap); + +} + +void guac_user_log(guac_user* user, guac_client_log_level level, + const char* format, ...) { + + va_list args; + va_start(args, format); + + vguac_client_log(user->client, level, format, args); + + va_end(args); + +} + +void guac_user_stream_png(guac_user* user, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface) { + + /* Allocate new stream for image */ + guac_stream* stream = guac_user_alloc_stream(user); + + /* Declare stream as containing image data */ + guac_protocol_send_img(socket, stream, mode, layer, "image/png", x, y); + + /* Write PNG data */ + guac_png_write(socket, stream, surface); + + /* Terminate stream */ + guac_protocol_send_end(socket, stream); + + /* Free allocated stream */ + guac_user_free_stream(user, stream); + +} + +void guac_user_stream_jpeg(guac_user* user, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface, int quality) { + + /* Allocate new stream for image */ + guac_stream* stream = guac_user_alloc_stream(user); + + /* Declare stream as containing image data */ + guac_protocol_send_img(socket, stream, mode, layer, "image/jpeg", x, y); + + /* Write JPEG data */ + guac_jpeg_write(socket, stream, surface, quality); + + /* Terminate stream */ + guac_protocol_send_end(socket, stream); + + /* Free allocated stream */ + guac_user_free_stream(user, stream); + +} + +void guac_user_stream_webp(guac_user* user, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface, int quality, int lossless) { + +#ifdef ENABLE_WEBP + /* Allocate new stream for image */ + guac_stream* stream = guac_user_alloc_stream(user); + + /* Declare stream as containing image data */ + guac_protocol_send_img(socket, stream, mode, layer, "image/webp", x, y); + + /* Write WebP data */ + guac_webp_write(socket, stream, surface, quality, lossless); + + /* Terminate stream */ + guac_protocol_send_end(socket, stream); + + /* Free allocated stream */ + guac_user_free_stream(user, stream); +#else + /* Do nothing if WebP support is not built in */ +#endif + +} + +int guac_user_supports_webp(guac_user* user) { + +#ifdef ENABLE_WEBP + const char** mimetype = user->info.image_mimetypes; + + /* Search for WebP mimetype in list of supported image mimetypes */ + while (*mimetype != NULL) { + + /* If WebP mimetype found, no need to search further */ + if (strcmp(*mimetype, "image/webp") == 0) + return 1; + + /* Next mimetype */ + mimetype++; + + } + + /* User does not support WebP */ + return 0; +#else + /* Support for WebP is completely absent */ + return 0; +#endif + +} + +char* guac_user_parse_args_string(guac_user* user, const char** arg_names, + const char** argv, int index, const char* default_value) { + + /* Pull parameter value from argv */ + const char* value = argv[index]; + + /* Use default value if blank */ + if (value[0] == 0) { + + /* NULL is a completely legal default value */ + if (default_value == NULL) + return NULL; + + /* Log use of default */ + guac_user_log(user, GUAC_LOG_DEBUG, "Parameter \"%s\" omitted. Using " + "default value of \"%s\".", arg_names[index], default_value); + + return strdup(default_value); + + } + + /* Otherwise use provided value */ + return strdup(value); + +} + +int guac_user_parse_args_int(guac_user* user, const char** arg_names, + const char** argv, int index, int default_value) { + + char* parse_end; + long parsed_value; + + /* Pull parameter value from argv */ + const char* value = argv[index]; + + /* Use default value if blank */ + if (value[0] == 0) { + + /* Log use of default */ + guac_user_log(user, GUAC_LOG_DEBUG, "Parameter \"%s\" omitted. Using " + "default value of %i.", arg_names[index], default_value); + + return default_value; + + } + + /* Parse value, checking for errors */ + errno = 0; + parsed_value = strtol(value, &parse_end, 10); + + /* Ensure parsed value is within the legal range of an int */ + if (parsed_value < INT_MIN || parsed_value > INT_MAX) + errno = ERANGE; + + /* Resort to default if input is invalid */ + if (errno != 0 || *parse_end != '\0') { + + /* Log use of default */ + guac_user_log(user, GUAC_LOG_WARNING, "Specified value \"%s\" for " + "parameter \"%s\" is not a valid integer. Using default value " + "of %i.", value, arg_names[index], default_value); + + return default_value; + + } + + /* Parsed successfully */ + return parsed_value; + +} + +int guac_user_parse_args_boolean(guac_user* user, const char** arg_names, + const char** argv, int index, int default_value) { + + /* Pull parameter value from argv */ + const char* value = argv[index]; + + /* Use default value if blank */ + if (value[0] == 0) { + + /* Log use of default */ + guac_user_log(user, GUAC_LOG_DEBUG, "Parameter \"%s\" omitted. Using " + "default value of %i.", arg_names[index], default_value); + + return default_value; + + } + + /* Parse string "true" as true */ + if (strcmp(value, "true") == 0) + return 1; + + /* Parse string "false" as false */ + if (strcmp(value, "false") == 0) + return 0; + + /* All other values are invalid */ + guac_user_log(user, GUAC_LOG_WARNING, "Parameter \"%s\" must be either " + "\"true\" or \"false\". Using default value.", arg_names[index]); + + return default_value; + +} + diff --git a/tests/Makefile.am b/tests/Makefile.am index b9a2bf4d..4e0396c4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -28,7 +28,6 @@ check_PROGRAMS = test_libguac noinst_HEADERS = \ client/client_suite.h \ - common/common_suite.h \ protocol/suite.h \ util/util_suite.h @@ -37,10 +36,6 @@ test_libguac_SOURCES = \ client/client_suite.c \ client/buffer_pool.c \ client/layer_pool.c \ - common/common_suite.c \ - common/guac_iconv.c \ - common/guac_string.c \ - common/guac_rect.c \ protocol/suite.c \ protocol/base64_decode.c \ protocol/instruction_parse.c \ @@ -53,11 +48,9 @@ test_libguac_SOURCES = \ test_libguac_CFLAGS = \ -Werror -Wall -pedantic \ - @COMMON_INCLUDE@ \ @LIBGUAC_INCLUDE@ test_libguac_LDADD = \ - @COMMON_LTLIB@ \ @CUNIT_LIBS@ \ @LIBGUAC_LTLIB@ diff --git a/tests/protocol/instruction_parse.c b/tests/protocol/instruction_parse.c index 63eaf25c..05aa95aa 100644 --- a/tests/protocol/instruction_parse.c +++ b/tests/protocol/instruction_parse.c @@ -29,13 +29,13 @@ #include #include -#include +#include void test_instruction_parse() { - /* Allocate instruction space */ - guac_instruction* instruction = guac_instruction_alloc(); - CU_ASSERT_PTR_NOT_NULL_FATAL(instruction); + /* Allocate parser */ + guac_parser* parser = guac_parser_alloc(); + CU_ASSERT_PTR_NOT_NULL_FATAL(parser); /* Instruction input */ char buffer[] = "4.test,8.testdata,5.zxcvb,13.guacamoletest;XXXXXXXXXXXXXXXXXX"; @@ -46,7 +46,7 @@ void test_instruction_parse() { while (remaining > 18) { /* Parse more data */ - int parsed = guac_instruction_append(instruction, current, remaining); + int parsed = guac_parser_append(parser, current, remaining); if (parsed == 0) break; @@ -56,24 +56,24 @@ void test_instruction_parse() { } CU_ASSERT_EQUAL(remaining, 18); - CU_ASSERT_EQUAL(instruction->state, GUAC_INSTRUCTION_PARSE_COMPLETE); + CU_ASSERT_EQUAL(parser->state, GUAC_PARSE_COMPLETE); /* Parse is complete - no more data should be read */ - CU_ASSERT_EQUAL(guac_instruction_append(instruction, current, 18), 0); - CU_ASSERT_EQUAL(instruction->state, GUAC_INSTRUCTION_PARSE_COMPLETE); + CU_ASSERT_EQUAL(guac_parser_append(parser, current, 18), 0); + CU_ASSERT_EQUAL(parser->state, GUAC_PARSE_COMPLETE); /* Validate resulting structure */ - CU_ASSERT_EQUAL(instruction->argc, 3); - CU_ASSERT_PTR_NOT_NULL_FATAL(instruction->opcode); - CU_ASSERT_PTR_NOT_NULL_FATAL(instruction->argv[0]); - CU_ASSERT_PTR_NOT_NULL_FATAL(instruction->argv[1]); - CU_ASSERT_PTR_NOT_NULL_FATAL(instruction->argv[2]); + CU_ASSERT_EQUAL(parser->argc, 3); + CU_ASSERT_PTR_NOT_NULL_FATAL(parser->opcode); + CU_ASSERT_PTR_NOT_NULL_FATAL(parser->argv[0]); + CU_ASSERT_PTR_NOT_NULL_FATAL(parser->argv[1]); + CU_ASSERT_PTR_NOT_NULL_FATAL(parser->argv[2]); /* Validate resulting content */ - CU_ASSERT_STRING_EQUAL(instruction->opcode, "test"); - CU_ASSERT_STRING_EQUAL(instruction->argv[0], "testdata"); - CU_ASSERT_STRING_EQUAL(instruction->argv[1], "zxcvb"); - CU_ASSERT_STRING_EQUAL(instruction->argv[2], "guacamoletest"); + CU_ASSERT_STRING_EQUAL(parser->opcode, "test"); + CU_ASSERT_STRING_EQUAL(parser->argv[0], "testdata"); + CU_ASSERT_STRING_EQUAL(parser->argv[1], "zxcvb"); + CU_ASSERT_STRING_EQUAL(parser->argv[2], "guacamoletest"); } diff --git a/tests/protocol/instruction_read.c b/tests/protocol/instruction_read.c index 886cffe0..42c6acd9 100644 --- a/tests/protocol/instruction_read.c +++ b/tests/protocol/instruction_read.c @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include @@ -71,7 +71,7 @@ void test_instruction_read() { else { guac_socket* socket; - guac_instruction* instruction; + guac_parser* parser; close(wfd); @@ -79,28 +79,30 @@ void test_instruction_read() { socket = guac_socket_open(rfd); CU_ASSERT_PTR_NOT_NULL_FATAL(socket); + /* Allocate parser */ + parser = guac_parser_alloc(); + CU_ASSERT_PTR_NOT_NULL_FATAL(parser); + /* Read instruction */ - instruction = guac_instruction_read(socket, 1000000); - CU_ASSERT_PTR_NOT_NULL_FATAL(instruction); + CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0); /* Validate contents */ - CU_ASSERT_STRING_EQUAL(instruction->opcode, "test"); - CU_ASSERT_EQUAL_FATAL(instruction->argc, 3); - CU_ASSERT_STRING_EQUAL(instruction->argv[0], "a" UTF8_4 "b"); - CU_ASSERT_STRING_EQUAL(instruction->argv[1], "12345"); - CU_ASSERT_STRING_EQUAL(instruction->argv[2], "a" UTF8_8 "c"); + CU_ASSERT_STRING_EQUAL(parser->opcode, "test"); + CU_ASSERT_EQUAL_FATAL(parser->argc, 3); + CU_ASSERT_STRING_EQUAL(parser->argv[0], "a" UTF8_4 "b"); + CU_ASSERT_STRING_EQUAL(parser->argv[1], "12345"); + CU_ASSERT_STRING_EQUAL(parser->argv[2], "a" UTF8_8 "c"); /* Read another instruction */ - guac_instruction_free(instruction); - instruction = guac_instruction_read(socket, 1000000); + CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0); /* Validate contents */ - CU_ASSERT_STRING_EQUAL(instruction->opcode, "test2"); - CU_ASSERT_EQUAL_FATAL(instruction->argc, 2); - CU_ASSERT_STRING_EQUAL(instruction->argv[0], "hellohello"); - CU_ASSERT_STRING_EQUAL(instruction->argv[1], "worldworldworld"); + CU_ASSERT_STRING_EQUAL(parser->opcode, "test2"); + CU_ASSERT_EQUAL_FATAL(parser->argc, 2); + CU_ASSERT_STRING_EQUAL(parser->argv[0], "hellohello"); + CU_ASSERT_STRING_EQUAL(parser->argv[1], "worldworldworld"); - guac_instruction_free(instruction); + guac_parser_free(parser); guac_socket_free(socket); } diff --git a/tests/protocol/instruction_write.c b/tests/protocol/instruction_write.c index 8334bd4f..49afb8b8 100644 --- a/tests/protocol/instruction_write.c +++ b/tests/protocol/instruction_write.c @@ -30,7 +30,6 @@ #include #include -#include #include #include diff --git a/tests/protocol/nest_write.c b/tests/protocol/nest_write.c index ebe701c7..11d403b5 100644 --- a/tests/protocol/nest_write.c +++ b/tests/protocol/nest_write.c @@ -30,7 +30,6 @@ #include #include -#include #include #include diff --git a/tests/test_libguac.c b/tests/test_libguac.c index b1cfd1ac..4d6084ea 100644 --- a/tests/test_libguac.c +++ b/tests/test_libguac.c @@ -39,7 +39,6 @@ int main() { register_protocol_suite(); register_client_suite(); register_util_suite(); - register_common_suite(); /* Run tests */ CU_basic_set_mode(CU_BRM_VERBOSE);