GUAC-1389: Refactor libguac to allow screen sharing.

This commit is contained in:
Michael Jumper 2016-02-29 21:35:32 -08:00
parent e8cd669be2
commit fbe4d53fb1
42 changed files with 3649 additions and 1837 deletions

View File

@ -37,32 +37,30 @@ DIST_SUBDIRS = \
SUBDIRS = \ SUBDIRS = \
src/libguac \ src/libguac \
src/common \
src/guacd \
tests tests
if ENABLE_COMMON_SSH if ENABLE_COMMON_SSH
SUBDIRS += src/common-ssh #SUBDIRS += src/common-ssh
endif endif
if ENABLE_TERMINAL if ENABLE_TERMINAL
SUBDIRS += src/terminal #SUBDIRS += src/terminal
endif endif
if ENABLE_RDP if ENABLE_RDP
SUBDIRS += src/protocols/rdp #SUBDIRS += src/protocols/rdp
endif endif
if ENABLE_SSH if ENABLE_SSH
SUBDIRS += src/protocols/ssh #SUBDIRS += src/protocols/ssh
endif endif
if ENABLE_TELNET if ENABLE_TELNET
SUBDIRS += src/protocols/telnet #SUBDIRS += src/protocols/telnet
endif endif
if ENABLE_VNC if ENABLE_VNC
SUBDIRS += src/protocols/vnc #SUBDIRS += src/protocols/vnc
endif endif
EXTRA_DIST = \ EXTRA_DIST = \

View File

@ -230,6 +230,9 @@ AC_ARG_WITH([pulse],
[], [],
[with_pulse=check]) [with_pulse=check])
# FIXME: Temporarily disabled
with_pulse=no
if test "x$with_pulse" != "xno" if test "x$with_pulse" != "xno"
then then
have_pulse=yes have_pulse=yes

View File

@ -38,16 +38,15 @@ libguacinc_HEADERS = \
guacamole/error.h \ guacamole/error.h \
guacamole/error-types.h \ guacamole/error-types.h \
guacamole/hash.h \ guacamole/hash.h \
guacamole/instruction-constants.h \
guacamole/instruction.h \
guacamole/instruction-types.h \
guacamole/layer.h \ guacamole/layer.h \
guacamole/layer-types.h \ guacamole/layer-types.h \
guacamole/object.h \ guacamole/object.h \
guacamole/object-types.h \ guacamole/object-types.h \
guacamole/parser-constants.h \
guacamole/parser.h \
guacamole/parser-types.h \
guacamole/plugin-constants.h \ guacamole/plugin-constants.h \
guacamole/plugin.h \ guacamole/plugin.h \
guacamole/plugin-types.h \
guacamole/pool.h \ guacamole/pool.h \
guacamole/pool-types.h \ guacamole/pool-types.h \
guacamole/protocol.h \ guacamole/protocol.h \
@ -60,34 +59,39 @@ libguacinc_HEADERS = \
guacamole/stream-types.h \ guacamole/stream-types.h \
guacamole/timestamp.h \ guacamole/timestamp.h \
guacamole/timestamp-types.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 = \ noinst_HEADERS = \
client-handlers.h \ id.h \
encode-jpeg.h \ encode-jpeg.h \
encode-png.h \ encode-png.h \
palette.h \ palette.h \
user-handlers.h \
raw_encoder.h raw_encoder.h
# Temporarily removed audio.c and raw_encoder.c for testing of others
libguac_la_SOURCES = \ libguac_la_SOURCES = \
audio.c \
client.c \ client.c \
client-handlers.c \
encode-jpeg.c \ encode-jpeg.c \
encode-png.c \ encode-png.c \
error.c \ error.c \
hash.c \ hash.c \
instruction.c \ id.c \
palette.c \ palette.c \
plugin.c \ parser.c \
pool.c \ pool.c \
protocol.c \ protocol.c \
raw_encoder.c \
socket.c \ socket.c \
socket-fd.c \ socket-fd.c \
socket-nest.c \ socket-nest.c \
timestamp.c \ timestamp.c \
unicode.c unicode.c \
user.c \
user-handlers.c
# Compile WebP support if available # Compile WebP support if available
if ENABLE_WEBP if ENABLE_WEBP

View File

@ -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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
/* 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;
}

View File

@ -23,29 +23,22 @@
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "client-handlers.h"
#include "encode-jpeg.h" #include "encode-jpeg.h"
#include "encode-png.h" #include "encode-png.h"
#include "encode-webp.h"
#include "error.h" #include "error.h"
#include "instruction.h" #include "id.h"
#include "layer.h" #include "layer.h"
#include "object.h"
#include "pool.h" #include "pool.h"
#include "plugin.h"
#include "protocol.h" #include "protocol.h"
#include "socket.h" #include "socket.h"
#include "stream.h" #include "stream.h"
#include "timestamp.h" #include "timestamp.h"
#include "user.h"
#ifdef ENABLE_WEBP #include <dlfcn.h>
#include "encode-webp.h" #include <pthread.h>
#endif
#ifdef HAVE_OSSP_UUID_H
#include <ossp/uuid.h>
#else
#include <uuid.h>
#endif
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -57,6 +50,23 @@ guac_layer __GUAC_DEFAULT_LAYER = {
const guac_layer* GUAC_DEFAULT_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) { guac_layer* guac_client_alloc_layer(guac_client* client) {
/* Init new layer */ /* Init new layer */
@ -109,9 +119,9 @@ guac_stream* guac_client_alloc_stream(guac_client* client) {
/* Allocate stream */ /* Allocate stream */
stream_index = guac_pool_next_int(client->__stream_pool); 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 = &(client->__output_streams[stream_index]);
allocd_stream->index = stream_index; allocd_stream->index = (stream_index * 2) + 1;
allocd_stream->data = NULL; allocd_stream->data = NULL;
allocd_stream->ack_handler = NULL; allocd_stream->ack_handler = NULL;
allocd_stream->blob_handler = NULL; allocd_stream->blob_handler = NULL;
@ -124,107 +134,132 @@ guac_stream* guac_client_alloc_stream(guac_client* client) {
void guac_client_free_stream(guac_client* client, guac_stream* stream) { void guac_client_free_stream(guac_client* client, guac_stream* stream) {
/* Release index to pool */ /* 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 */ /* Mark stream as closed */
stream->index = GUAC_CLIENT_CLOSED_STREAM_INDEX; stream->index = GUAC_CLIENT_CLOSED_STREAM_INDEX;
} }
guac_object* guac_client_alloc_object(guac_client* client) {
guac_object* allocd_object; /**
int object_index; * The broadcast socket cannot be read from.
*/
static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket,
void* buf, size_t count) {
/* Refuse to allocate beyond maximum */ /* Broadcast socket reads are not allowed */
if (client->__object_pool->active == GUAC_CLIENT_MAX_OBJECTS) return -1;
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;
} }
/** /**
* Returns a newly allocated string containing a guaranteed-unique connection * Writes a chunk of data to a given user.
* identifier string which is 37 characters long and begins with a '$'
* character. If an error occurs, NULL is returned, and no memory is
* allocated.
*/ */
static char* __guac_generate_connection_id() { static void* __write_chunk_callback(guac_user* user, void* data) {
char* buffer; __write_chunk* chunk = (__write_chunk*) data;
char* identifier;
size_t identifier_length;
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; * Socket write handler which operates on each of the sockets of all connected
guac_error_message = "UUID generation failed"; * users, unifying the results.
*/
static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket,
const void* buf, size_t count) {
guac_client* client = (guac_client*) socket->data;
/* Build chunk */
__write_chunk chunk;
chunk.buffer = buf;
chunk.length = count;
/* Broadcast chunk to all users */
guac_client_foreach_user(client, __write_chunk_callback, &chunk);
return count;
}
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; return NULL;
}
/* Allocate buffer for future formatted ID */ }
buffer = malloc(UUID_LEN_STR + 2);
if (buffer == NULL) { static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) {
uuid_destroy(uuid);
guac_error = GUAC_STATUS_NO_MEMORY; guac_client* client = (guac_client*) socket->data;
guac_error_message = "Could not allocate memory for connection ID";
/* Flush all users */
guac_client_foreach_user(client, __flush_callback, NULL);
return 0;
}
static void* __lock_callback(guac_user* user, void* data) {
/* Lock socket */
guac_socket_instruction_begin(user->socket);
return NULL; return NULL;
}
identifier = &(buffer[1]); }
identifier_length = UUID_LEN_STR + 1;
static void __guac_socket_broadcast_lock_handler(guac_socket* socket) {
guac_client* client = (guac_client*) socket->data;
/* Flush all users */
guac_client_foreach_user(client, __lock_callback, NULL);
}
static void* __unlock_callback(guac_user* user, void* data) {
/* Lock socket */
guac_socket_instruction_end(user->socket);
/* 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; return NULL;
}
uuid_destroy(uuid); }
buffer[0] = '$'; static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) {
buffer[UUID_LEN_STR + 1] = '\0';
return buffer; guac_client* client = (guac_client*) socket->data;
/* Flush all users */
guac_client_foreach_user(client, __unlock_callback, NULL);
}
/**
* The broadcast socket cannot be read from (nor selected).
*/
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() { guac_client* guac_client_alloc() {
int i; int i;
pthread_rwlockattr_t lock_attributes;
/* Allocate new client */ /* Allocate new client */
guac_client* client = malloc(sizeof(guac_client)); guac_client* client = malloc(sizeof(guac_client));
@ -237,13 +272,11 @@ guac_client* guac_client_alloc() {
/* Init new client */ /* Init new client */
memset(client, 0, sizeof(guac_client)); memset(client, 0, sizeof(guac_client));
client->last_received_timestamp = client->state = GUAC_CLIENT_RUNNING;
client->last_sent_timestamp = guac_timestamp_current(); client->last_sent_timestamp = guac_timestamp_current();
client->state = GUAC_CLIENT_RUNNING;
/* Generate ID */ /* Generate ID */
client->connection_id = __guac_generate_connection_id(); client->connection_id = guac_generate_id(GUAC_CLIENT_ID_PREFIX);
if (client->connection_id == NULL) { if (client->connection_id == NULL) {
free(client); free(client);
return NULL; return NULL;
@ -257,21 +290,30 @@ guac_client* guac_client_alloc() {
client->__stream_pool = guac_pool_alloc(0); client->__stream_pool = guac_pool_alloc(0);
/* Initialize streams */ /* Initialize streams */
client->__input_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS);
client->__output_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS); client->__output_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS);
for (i=0; i<GUAC_CLIENT_MAX_STREAMS; i++) { for (i=0; i<GUAC_CLIENT_MAX_STREAMS; i++) {
client->__input_streams[i].index = GUAC_CLIENT_CLOSED_STREAM_INDEX;
client->__output_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 */ /* Init locks */
client->__objects = malloc(sizeof(guac_object) * GUAC_CLIENT_MAX_OBJECTS); pthread_rwlockattr_init(&lock_attributes);
for (i=0; i<GUAC_CLIENT_MAX_OBJECTS; i++) pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
client->__objects[i].index = GUAC_CLIENT_UNDEFINED_OBJECT_INDEX;
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; return client;
@ -279,6 +321,10 @@ guac_client* guac_client_alloc() {
void guac_client_free(guac_client* client) { 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) { if (client->free_handler) {
/* FIXME: Errors currently ignored... */ /* FIXME: Errors currently ignored... */
@ -291,37 +337,19 @@ void guac_client_free(guac_client* client) {
guac_pool_free(client->__layer_pool); guac_pool_free(client->__layer_pool);
/* Free streams */ /* Free streams */
free(client->__input_streams);
free(client->__output_streams); free(client->__output_streams);
/* Free stream pool */ /* Free stream pool */
guac_pool_free(client->__stream_pool); guac_pool_free(client->__stream_pool);
/* Free objects */ /* Close associated plugin */
free(client->__objects); if (client->__plugin_handle != NULL) {
if (dlclose(client->__plugin_handle))
/* Free object pool */ guac_client_log(client, GUAC_LOG_ERROR, "Unable to close plugin: %s", dlerror());
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++;
} }
/* If unrecognized, ignore */ pthread_rwlock_destroy(&(client->__users_lock));
return 0; free(client);
} }
void vguac_client_log(guac_client* client, guac_client_log_level level, void vguac_client_log(guac_client* client, guac_client_log_level level,
@ -381,6 +409,198 @@ 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;
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.
*/
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, void guac_client_stream_png(guac_client* client, guac_socket* socket,
guac_composite_mode mode, const guac_layer* layer, int x, int y, guac_composite_mode mode, const guac_layer* layer, int x, int y,
cairo_surface_t* surface) { cairo_surface_t* surface) {
@ -448,25 +668,42 @@ 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.
*/
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) { int guac_client_supports_webp(guac_client* client) {
#ifdef ENABLE_WEBP #ifdef ENABLE_WEBP
char** mimetype = client->info.image_mimetypes; int webp_supported = 1;
/* Search for WebP mimetype in list of supported image mimetypes */ /* WebP is supported for entire client only if each user supports it */
while (*mimetype != NULL) { guac_client_foreach_user(client, __webp_support_callback, &webp_supported);
/* If WebP mimetype found, no need to search further */ return webp_supported;
if (strcmp(*mimetype, "image/webp") == 0)
return 1;
/* Next mimetype */
mimetype++;
}
/* Client does not support WebP */
return 0;
#else #else
/* Support for WebP is completely absent */ /* Support for WebP is completely absent */
return 0; return 0;

View File

@ -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 #define GUAC_CLIENT_MAX_STREAMS 64
@ -40,26 +41,9 @@
#define GUAC_CLIENT_CLOSED_STREAM_INDEX -1 #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 #define GUAC_CLIENT_ID_PREFIX '$'
/**
* 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"
/** /**
* The flag set in the mouse button mask when the left mouse button is down. * The flag set in the mouse button mask when the left mouse button is down.

View File

@ -34,87 +34,10 @@
#include "object-types.h" #include "object-types.h"
#include "protocol-types.h" #include "protocol-types.h"
#include "stream-types.h" #include "stream-types.h"
#include "user-types.h"
#include <stdarg.h> #include <stdarg.h>
/**
* 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 * Handler for freeing up any extra data allocated by the client
* implementation. * implementation.
@ -122,14 +45,14 @@ typedef int guac_client_video_handler(guac_client* client, char* mimetype);
typedef int guac_client_free_handler(guac_client* client); typedef int guac_client_free_handler(guac_client* client);
/** /**
* Handler for logging messages * Handler for logging messages.
*/ */
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. * Handler which should initialize the given guac_client.
*/ */
typedef int guac_client_init_handler(guac_client* client, int argc, char** argv); typedef int guac_client_init_handler(guac_client* client);
#endif #endif

View File

@ -87,11 +87,5 @@ typedef enum guac_client_log_level {
} 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 #endif

View File

@ -32,73 +32,34 @@
#include "client-fntypes.h" #include "client-fntypes.h"
#include "client-types.h" #include "client-types.h"
#include "client-constants.h" #include "client-constants.h"
#include "instruction-types.h"
#include "layer-types.h" #include "layer-types.h"
#include "object-types.h" #include "object-types.h"
#include "pool-types.h" #include "pool-types.h"
#include "socket-types.h" #include "socket-types.h"
#include "stream-types.h" #include "stream-types.h"
#include "timestamp-types.h" #include "timestamp-types.h"
#include "user-fntypes.h"
#include "user-types.h"
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <pthread.h>
#include <stdarg.h> #include <stdarg.h>
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 { struct guac_client {
/** /**
* The guac_socket structure to be used to communicate with the web-client. * The guac_socket structure to be used to communicate with all connected
* It is expected that the implementor of any Guacamole proxy client will * 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 * provide their own mechanism of I/O for their protocol. The guac_socket
* structure is used only to communicate conveniently with the Guacamole * structure is used only to communicate conveniently with the Guacamole
* web-client. * 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; guac_socket* socket;
@ -110,24 +71,6 @@ struct guac_client {
*/ */
guac_client_state state; 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 * Arbitrary reference to proxy client-specific data. Implementors of a
* Guacamole proxy client can store any data they want here, which can then * Guacamole proxy client can store any data they want here, which can then
@ -136,197 +79,10 @@ struct guac_client {
void* data; void* data;
/** /**
* Handler for server messages. If set, this function will be called * The time (in milliseconds) that the last sync message was sent to the
* occasionally by the Guacamole proxy to give the client a chance to * client.
* 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
*/ */
guac_client_handle_messages* handle_messages; guac_timestamp last_sent_timestamp;
/**
* 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:
*
* <table>
* <tr><th>Button</th> <th>Value</th></tr>
* <tr><td>Left</td> <td>1</td></tr>
* <tr><td>Middle</td> <td>2</td></tr>
* <tr><td>Right</td> <td>4</td></tr>
* <tr><td>Scrollwheel Up</td> <td>8</td></tr>
* <tr><td>Scrollwheel Down</td><td>16</td></tr>
* </table>
* 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;
/** /**
* Handler for freeing data when the client is being unloaded. * Handler for freeing data when the client is being unloaded.
@ -344,7 +100,7 @@ struct guac_client {
* @code * @code
* int free_handler(guac_client* client); * 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; * client->free_handler = free_handler;
* } * }
* @endcode * @endcode
@ -375,46 +131,6 @@ struct guac_client {
*/ */
guac_client_log_handler* log_handler; 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. * Pool of buffer indices. Buffers are simply layers with negative indices.
* Note that because guac_pool always gives non-negative indices starting * Note that because guac_pool always gives non-negative indices starting
@ -435,25 +151,11 @@ struct guac_client {
guac_pool* __stream_pool; 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; 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 * The unique identifier allocated for the connection, which may
* be used within the Guacamole protocol to refer to this connection. * be used within the Guacamole protocol to refer to this connection.
@ -463,6 +165,106 @@ struct guac_client {
*/ */
char* connection_id; 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 +282,6 @@ guac_client* guac_client_alloc();
*/ */
void guac_client_free(guac_client* client); 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 * 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) * normally be defined by guacd (or whichever program loads the proxy client)
@ -604,8 +387,11 @@ void guac_client_free_layer(guac_client* client, guac_layer* layer);
* Allocates a new stream. An arbitrary index is automatically assigned * Allocates a new stream. An arbitrary index is automatically assigned
* if no previously-allocated stream is available for use. * if no previously-allocated stream is available for use.
* *
* @param client The proxy client to allocate the layer buffer for. * @param client
* @return The next available stream, or a newly allocated stream. * 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); guac_stream* guac_client_alloc_stream(guac_client* client);
@ -613,34 +399,134 @@ guac_stream* guac_client_alloc_stream(guac_client* client);
* Returns the given stream to the pool of available streams, such that it * 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(). * can be reused by any subsequent call to guac_client_alloc_stream().
* *
* @param client The proxy client to return the buffer to. * @param client
* @param stream The stream to return to the pool of available stream. * 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); void guac_client_free_stream(guac_client* client, guac_stream* stream);
/** /**
* Allocates a new object. An arbitrary index is automatically assigned * Adds the given user to the internal list of connected users. Future writes
* if no previously-allocated object is available for use. * 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 * @param client The proxy client to add the user to.
* The proxy client to allocate the object for. * @param user The user to add.
* * @param argc The number of arguments to pass to the new user.
* @return * @param argv An array of strings containing the argument values being passed.
* The next available object, or a newly allocated object. * @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 * Removes the given user, removing the user from the internally-tracked list
* can be reused by any subsequent call to guac_client_alloc_object(). * 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 * @param client
* The proxy client to return the object to. * The client whose users should be iterated.
* *
* @param object * @param callback
* The object to return to the pool of available object. * 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" * Streams the image data of the given surface over an image stream ("img"
@ -648,7 +534,7 @@ void guac_client_free_object(guac_client* client, guac_object* object);
* allocated and freed. * allocated and freed.
* *
* @param client * @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 * @param socket
* The socket over which instructions associated with the image stream * The socket over which instructions associated with the image stream
@ -681,7 +567,7 @@ void guac_client_stream_png(guac_client* client, guac_socket* socket,
* will be automatically allocated and freed. * will be automatically allocated and freed.
* *
* @param client * @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 * @param socket
* The socket over which instructions associated with the image stream * The socket over which instructions associated with the image stream
@ -705,8 +591,9 @@ void guac_client_stream_png(guac_client* client, guac_socket* socket,
* A Cairo surface containing the image data to be streamed. * A Cairo surface containing the image data to be streamed.
* *
* @param quality * @param quality
* The JPEG image quality, which must be an integer value between 0 and * The JPEG image quality, which must be an integer value between 0 and 100
* 100 inclusive. * inclusive. Larger values indicate improving quality at the expense of
* larger file size.
*/ */
void guac_client_stream_jpeg(guac_client* client, guac_socket* socket, void guac_client_stream_jpeg(guac_client* client, guac_socket* socket,
guac_composite_mode mode, const guac_layer* layer, int x, int y, guac_composite_mode mode, const guac_layer* layer, int x, int y,
@ -717,10 +604,11 @@ void guac_client_stream_jpeg(guac_client* client, guac_socket* socket,
* instruction) as WebP-encoded data at the given quality. The image stream * instruction) as WebP-encoded data at the given quality. The image stream
* will be automatically allocated and freed. If the server does not support * 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 * WebP, this function has no effect, so be sure to check the result of
* guac_client_supports_webp() prior to calling this function. * guac_client_supports_webp() prior to calling
* this function.
* *
* @param client * @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 * @param socket
* The socket over which instructions associated with the image stream * The socket over which instructions associated with the image stream
@ -758,15 +646,15 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket,
cairo_surface_t* surface, int quality, int lossless); cairo_surface_t* surface, int quality, int lossless);
/** /**
* Returns whether the given client supports WebP. If the client does not * Returns whether all users of the given client support WebP. If any user does
* support WebP, or the server cannot encode WebP images, zero is returned. * not support WebP, or the server cannot encode WebP images, zero is returned.
* *
* @param client * @param client
* The Guacamole client to check for WebP support. * The Guacamole client whose users should be checked for WebP support.
* *
* @return * @return
* Non-zero if the given client claims to support WebP and the server has * Non-zero if the all users of the given client claim to support WebP and
* been built with WebP support, zero otherwise. * the server has been built with WebP support, zero otherwise.
*/ */
int guac_client_supports_webp(guac_client* client); int guac_client_supports_webp(guac_client* client);

View File

@ -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

View File

@ -29,8 +29,8 @@
* @file object.h * @file object.h
*/ */
#include "client-fntypes.h"
#include "object-types.h" #include "object-types.h"
#include "user-fntypes.h"
struct guac_object { struct guac_object {
@ -54,18 +54,18 @@ struct guac_object {
* *
* Example: * Example:
* @code * @code
* int get_handler(guac_client* client, guac_object* object, * int get_handler(guac_user* user, guac_object* object,
* char* name); * 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; * object->get_handler = get_handler;
* *
* } * }
* @endcode * @endcode
*/ */
guac_client_get_handler* get_handler; guac_user_get_handler* get_handler;
/** /**
* Handler for put events sent by the Guacamole web-client. * Handler for put events sent by the Guacamole web-client.
@ -77,18 +77,18 @@ struct guac_object {
* *
* Example: * Example:
* @code * @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); * 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; * object->put_handler = put_handler;
* *
* } * }
* @endcode * @endcode
*/ */
guac_client_put_handler* put_handler; guac_user_put_handler* put_handler;
}; };

View File

@ -20,13 +20,13 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef _GUAC_INSTRUCTION_CONSTANTS_H #ifndef _GUAC_PARSER_CONSTANTS_H
#define _GUAC_INSTRUCTION_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
*/ */
/** /**

View File

@ -21,48 +21,49 @@
*/ */
#ifndef _GUAC_INSTRUCTION_TYPES_H #ifndef _GUAC_PARSER_TYPES_H
#define _GUAC_INSTRUCTION_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. * 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 * The parser is currently waiting for data to complete the length prefix
* of the current element of the instruction. * 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 * The parser has finished reading the length prefix and is currently
* waiting for data to complete the content of the instruction. * waiting for data to complete the content of the instruction.
*/ */
GUAC_INSTRUCTION_PARSE_CONTENT, GUAC_PARSE_CONTENT,
/** /**
* The instruction has been fully parsed. * The instruction has been fully parsed.
*/ */
GUAC_INSTRUCTION_PARSE_COMPLETE, GUAC_PARSE_COMPLETE,
/** /**
* The instruction cannot be parsed because of a protocol error. * 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 #endif

View File

@ -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

View File

@ -23,9 +23,7 @@
#ifndef _GUAC_PLUGIN_H #ifndef _GUAC_PLUGIN_H
#define _GUAC_PLUGIN_H #define _GUAC_PLUGIN_H
#include "client-types.h"
#include "plugin-constants.h" #include "plugin-constants.h"
#include "plugin-types.h"
/** /**
* Provides functions and structures required for handling a client plugin. * Provides functions and structures required for handling a client plugin.
@ -33,60 +31,4 @@
* @file plugin.h * @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 #endif

View File

@ -32,6 +32,8 @@
#include "pool-types.h" #include "pool-types.h"
#include <pthread.h>
struct guac_pool { struct guac_pool {
/** /**
@ -62,6 +64,11 @@ struct guac_pool {
*/ */
guac_pool_int* __tail; guac_pool_int* __tail;
/**
* Lock which is acquired when the pool is being modified or accessed.
*/
pthread_mutex_t __lock;
}; };
struct guac_pool_int { struct guac_pool_int {
@ -99,9 +106,13 @@ void guac_pool_free(guac_pool* pool);
/** /**
* Returns the next available integer from the given guac_pool. All integers * Returns the next available integer from the given guac_pool. All integers
* returned are non-negative, and are returned in sequences, starting from 0. * 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. * @param pool
* @return The next available integer, which may be either an integer not yet * 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 * returned by a call to guac_pool_next_int, or an integer which was
* previosly returned, but has since been freed. * previosly returned, but has since been freed.
*/ */
@ -109,11 +120,15 @@ int guac_pool_next_int(guac_pool* pool);
/** /**
* Frees the given integer back into the given guac_pool. The integer given * 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 pool
* @param value The integer which should be returned to the given pool, such * The guac_pool to free the given integer into.
* that it can be received by a future call to guac_pool_next_int. *
* @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); void guac_pool_free_int(guac_pool* pool, int value);

View File

@ -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 * 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 * 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 socket The guac_socket being written to.
* @param buf The arbitrary buffer containing data to be written. * @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. * @return The number of bytes written, or -1 if an error occurs.
*/ */
typedef ssize_t guac_socket_write_handler(guac_socket* socket, 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); 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 * 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 * POSIX close() function. When set within a guac_socket, a handler of this type

View File

@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * 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. * 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; 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. * on this socket.
*/ */
guac_socket_select_handler* select_handler; guac_socket_select_handler* select_handler;
@ -90,52 +105,6 @@ struct guac_socket {
*/ */
int __ready_buf[3]; 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. * Whether automatic keep-alive is enabled.
*/ */
@ -164,66 +133,36 @@ guac_socket* guac_socket_alloc();
*/ */
void guac_socket_free(guac_socket* socket); 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 * 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. * 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 * This ping will take the form of a "nop" instruction.
* automatically enables threadsafety.
* *
* @param socket The guac_socket to declare as requiring an automatic * @param socket
* keep-alive ping. * The guac_socket to declare as requiring an automatic keep-alive ping.
*/ */
void guac_socket_require_keep_alive(guac_socket* socket); void guac_socket_require_keep_alive(guac_socket* socket);
/** /**
* Marks the beginning of a Guacamole protocol instruction. If threadsafety * Marks the beginning of a Guacamole protocol instruction.
* is enabled on the socket, other instructions will be blocked from sending
* until this instruction is complete.
* *
* @param socket The guac_socket beginning an instruction. * @param socket
* The guac_socket beginning an instruction.
*/ */
void guac_socket_instruction_begin(guac_socket* socket); void guac_socket_instruction_begin(guac_socket* socket);
/** /**
* Marks the end of a Guacamole protocol instruction. If threadsafety * Marks the end of a Guacamole protocol instruction.
* is enabled on the socket, other instructions will be allowed to send.
* *
* @param socket The guac_socket ending an instruction. * @param socket
* The guac_socket ending an instruction.
*/ */
void guac_socket_instruction_end(guac_socket* socket); 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 * 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, * If an error occurs while allocating the guac_socket object, NULL is returned,
* and guac_error is set appropriately. * 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 * 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, * If an error occurs while allocating the guac_socket object, NULL is returned,
* and guac_error is set appropriately. * 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 * Writes the given string to the given guac_socket object. The data
* written may be buffered until the buffer is flushed automatically or * written may be buffered until the buffer is flushed automatically or
* manually. Note that if the string can contain characters used * manually.
* internally by the Guacamole protocol (commas, semicolons, or
* backslashes) it will need to be escaped.
* *
* If an error occurs while writing, a non-zero value is returned, and * If an error occurs while writing, a non-zero value is returned, and
* guac_error is set appropriately. * 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- * 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 * 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() * 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. * base64 data) can be made.
* *
* If an error occurs while writing, a non-zero value is returned, and * 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); 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 * Writes the given data to the specified socket. The data written may be
* buffered, and will be sent immediately. * buffered until the buffer is flushed automatically or manually.
* *
* If an error occurs while writing, a non-zero value is returned, and * If an error occurs while writing, a non-zero value is returned, and
* guac_error is set appropriately. * guac_error is set appropriately.

View File

@ -29,7 +29,7 @@
* @file stream.h * @file stream.h
*/ */
#include "client-fntypes.h" #include "user-fntypes.h"
#include "stream-types.h" #include "stream-types.h"
struct guac_stream { struct guac_stream {
@ -53,21 +53,21 @@ struct guac_stream {
* *
* Example: * Example:
* @code * @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); * 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; * stream->ack_handler = ack_handler;
* *
* guac_protocol_send_clipboard(client->socket, * guac_protocol_send_clipboard(user->socket,
* stream, "text/plain"); * stream, "text/plain");
* *
* } * }
* @endcode * @endcode
*/ */
guac_client_ack_handler* ack_handler; guac_user_ack_handler* ack_handler;
/** /**
* Handler for blob events sent by the Guacamole web-client. * Handler for blob events sent by the Guacamole web-client.
@ -78,16 +78,16 @@ struct guac_stream {
* *
* Example: * Example:
* @code * @code
* int blob_handler(guac_client* client, guac_stream* stream, * int blob_handler(guac_user* user, guac_stream* stream,
* void* data, int length); * 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) { * char* mimetype) {
* stream->blob_handler = blob_handler; * stream->blob_handler = blob_handler;
* } * }
* @endcode * @endcode
*/ */
guac_client_blob_handler* blob_handler; guac_user_blob_handler* blob_handler;
/** /**
* Handler for stream end events sent by the Guacamole web-client. * Handler for stream end events sent by the Guacamole web-client.
@ -98,15 +98,15 @@ struct guac_stream {
* *
* Example: * Example:
* @code * @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) { * char* mimetype) {
* stream->end_handler = end_handler; * stream->end_handler = end_handler;
* } * }
* @endcode * @endcode
*/ */
guac_client_end_handler* end_handler; guac_user_end_handler* end_handler;
}; };

View File

@ -37,9 +37,18 @@
* calls. The return value from a single call will not have any useful * calls. The return value from a single call will not have any useful
* (or defined) meaning. * (or defined) meaning.
* *
* @return An arbitrary millisecond timestamp. * @return
* An arbitrary millisecond timestamp.
*/ */
guac_timestamp guac_timestamp_current(); 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 #endif

View File

@ -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

View File

@ -0,0 +1,140 @@
/*
* 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()
*/
typedef void* guac_user_callback(guac_user* user, void* data);
/**
* Handler for Guacamole mouse events.
*/
typedef int guac_user_mouse_handler(guac_user* user, int x, int y,
int button_mask);
/**
* Handler for Guacamole key events.
*/
typedef int guac_user_key_handler(guac_user* user, int keysym, int pressed);
/**
* Handler for Guacamole clipboard events.
*/
typedef int guac_user_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype);
/**
* Handler for Guacamole screen size events.
*/
typedef int guac_user_size_handler(guac_user* user,
int width, int height);
/**
* Handler for Guacamole file transfer events.
*/
typedef int guac_user_file_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* filename);
/**
* Handler for Guacamole pipe events.
*/
typedef int guac_user_pipe_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name);
/**
* Handler for Guacamole stream blob events.
*/
typedef int guac_user_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length);
/**
* Handler for Guacamole stream ack events.
*/
typedef int guac_user_ack_handler(guac_user* user, guac_stream* stream,
char* error, guac_protocol_status status);
/**
* Handler for Guacamole stream end events.
*/
typedef int guac_user_end_handler(guac_user* user, guac_stream* stream);
/**
* Handler for Guacamole audio format events.
*/
typedef int guac_user_audio_handler(guac_user* user, char* mimetype);
/**
* Handler for Guacamole video format events.
*/
typedef int guac_user_video_handler(guac_user* user, char* mimetype);
/**
* Handler for Guacamole join events. A join event is fired by the
* guac_client whenever a guac_user joins the connection.
*/
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.
*/
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.
*/
typedef int guac_user_sync_handler(guac_user* user, guac_timestamp timestamp);
/**
* Handler for Guacamole object get events.
*/
typedef int guac_user_get_handler(guac_user* user, guac_object* object,
char* name);
/**
* Handler for Guacamole object put events.
*/
typedef int guac_user_put_handler(guac_user* user, guac_object* object,
guac_stream* stream, char* mimetype, char* name);
#endif

View File

@ -20,21 +20,26 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#ifndef _GUAC_PLUGIN_TYPES_H #ifndef _GUAC_USER_TYPES_H
#define _GUAC_PLUGIN_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 * Representation of a physical connection within a larger logical connection
* plugin to complete the initial protocol handshake and instantiate a new * which may be shared. Logical connections are represented by guac_client.
* client supporting the protocol provided by the client plugin.
*/ */
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 #endif

View File

@ -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 <cairo/cairo.h>
#include <pthread.h>
#include <stdarg.h>
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:
*
* <table>
* <tr><th>Button</th> <th>Value</th></tr>
* <tr><td>Left</td> <td>1</td></tr>
* <tr><td>Middle</td> <td>2</td></tr>
* <tr><td>Right</td> <td>4</td></tr>
* <tr><td>Scrollwheel Up</td> <td>8</td></tr>
* <tr><td>Scrollwheel Down</td><td>16</td></tr>
* </table>
* 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

88
src/libguac/id.c Normal file
View File

@ -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 <ossp/uuid.h>
#else
#include <uuid.h>
#endif
#include <stdlib.h>
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;
}

40
src/libguac/id.h Normal file
View File

@ -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

View File

@ -23,7 +23,7 @@
#include "config.h" #include "config.h"
#include "error.h" #include "error.h"
#include "instruction.h" #include "parser.h"
#include "socket.h" #include "socket.h"
#include "unicode.h" #include "unicode.h"
@ -31,46 +31,49 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
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_parser* guac_parser_alloc() {
guac_instruction* instruction = malloc(sizeof(guac_instruction));
if (instruction == NULL) { /* Allocate space for parser */
guac_parser* parser = malloc(sizeof(guac_parser));
if (parser == NULL) {
guac_error = GUAC_STATUS_NO_MEMORY; guac_error = GUAC_STATUS_NO_MEMORY;
guac_error_message = "Insufficient memory to allocate instruction"; guac_error_message = "Insufficient memory to allocate parser";
return NULL; return NULL;
} }
guac_instruction_reset(instruction); /* Init parse start/end marksers */
return instruction; 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) { int guac_parser_append(guac_parser* parser, void* buffer, int length) {
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) {
char* char_buffer = (char*) buffer; char* char_buffer = (char*) buffer;
int bytes_parsed = 0; int bytes_parsed = 0;
/* Do not exceed maximum number of elements */ /* Do not exceed maximum number of elements */
if (instr->__elementc == GUAC_INSTRUCTION_MAX_ELEMENTS if (parser->__elementc == GUAC_INSTRUCTION_MAX_ELEMENTS
&& instr->state != GUAC_INSTRUCTION_PARSE_COMPLETE) { && parser->state != GUAC_PARSE_COMPLETE) {
instr->state = GUAC_INSTRUCTION_PARSE_ERROR; parser->state = GUAC_PARSE_ERROR;
return 0; return 0;
} }
/* Parse element length */ /* 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) { while (bytes_parsed < length) {
/* Pull next character */ /* Pull next character */
@ -83,14 +86,14 @@ int guac_instruction_append(guac_instruction* instr,
/* If period, switch to parsing content */ /* If period, switch to parsing content */
else if (c == '.') { else if (c == '.') {
instr->__elementv[instr->__elementc++] = char_buffer; parser->__elementv[parser->__elementc++] = char_buffer;
instr->state = GUAC_INSTRUCTION_PARSE_CONTENT; parser->state = GUAC_PARSE_CONTENT;
break; break;
} }
/* If not digit, parse error */ /* If not digit, parse error */
else { else {
instr->state = GUAC_INSTRUCTION_PARSE_ERROR; parser->state = GUAC_PARSE_ERROR;
return 0; return 0;
} }
@ -98,19 +101,19 @@ int guac_instruction_append(guac_instruction* instr,
/* If too long, parse error */ /* If too long, parse error */
if (parsed_length > GUAC_INSTRUCTION_MAX_LENGTH) { if (parsed_length > GUAC_INSTRUCTION_MAX_LENGTH) {
instr->state = GUAC_INSTRUCTION_PARSE_ERROR; parser->state = GUAC_PARSE_ERROR;
return 0; return 0;
} }
/* Save length */ /* Save length */
instr->__element_length = parsed_length; parser->__element_length = parsed_length;
} /* end parse length */ } /* end parse length */
/* Parse element content */ /* 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 */ /* Get length of current character */
char c = *char_buffer; char c = *char_buffer;
@ -124,35 +127,35 @@ int guac_instruction_append(guac_instruction* instr,
bytes_parsed += char_length; bytes_parsed += char_length;
/* If end of element, handle terminator */ /* If end of element, handle terminator */
if (instr->__element_length == 0) { if (parser->__element_length == 0) {
*char_buffer = '\0'; *char_buffer = '\0';
/* If semicolon, store end-of-instruction */ /* If semicolon, store end-of-instruction */
if (c == ';') { if (c == ';') {
instr->state = GUAC_INSTRUCTION_PARSE_COMPLETE; parser->state = GUAC_PARSE_COMPLETE;
instr->opcode = instr->__elementv[0]; parser->opcode = parser->__elementv[0];
instr->argv = &(instr->__elementv[1]); parser->argv = &(parser->__elementv[1]);
instr->argc = instr->__elementc - 1; parser->argc = parser->__elementc - 1;
break; break;
} }
/* If comma, move on to next element */ /* If comma, move on to next element */
else if (c == ',') { else if (c == ',') {
instr->state = GUAC_INSTRUCTION_PARSE_LENGTH; parser->state = GUAC_PARSE_LENGTH;
break; break;
} }
/* Otherwise, parse error */ /* Otherwise, parse error */
else { else {
instr->state = GUAC_INSTRUCTION_PARSE_ERROR; parser->state = GUAC_PARSE_ERROR;
return 0; return 0;
} }
} /* end if end of element */ } /* end if end of element */
/* Advance to next character */ /* Advance to next character */
instr->__element_length--; parser->__element_length--;
char_buffer += char_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. */ int guac_parser_read(guac_parser* parser, guac_socket* socket, int usec_timeout) {
guac_instruction* guac_instruction_read(guac_socket* socket,
int usec_timeout) {
char* unparsed_end = socket->__instructionbuf_unparsed_end; char* unparsed_end = parser->__instructionbuf_unparsed_end;
char* unparsed_start = socket->__instructionbuf_unparsed_start; char* unparsed_start = parser->__instructionbuf_unparsed_start;
char* instr_start = socket->__instructionbuf_unparsed_start; char* instr_start = parser->__instructionbuf_unparsed_start;
char* buffer_end = socket->__instructionbuf char* buffer_end = parser->__instructionbuf + sizeof(parser->__instructionbuf);
+ sizeof(socket->__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 while (parser->state != GUAC_PARSE_COMPLETE
&& instruction->state != GUAC_INSTRUCTION_PARSE_ERROR) { && parser->state != GUAC_PARSE_ERROR) {
/* Add any available data to buffer */ /* Add any available data to buffer */
int parsed = guac_instruction_append(instruction, unparsed_start, int parsed = guac_parser_append(parser, unparsed_start, unparsed_end - unparsed_start);
unparsed_end - unparsed_start);
/* Read more data if not enough data to parse */ /* Read more data if not enough data to parse */
if (parsed == 0) { if (parsed == 0 && parser->state != GUAC_PARSE_ERROR) {
int retval; int retval;
@ -191,23 +192,23 @@ guac_instruction* guac_instruction_read(guac_socket* socket,
if (unparsed_end == buffer_end) { if (unparsed_end == buffer_end) {
/* Shift backward if possible */ /* Shift backward if possible */
if (instr_start != socket->__instructionbuf) { if (instr_start != parser->__instructionbuf) {
int i; int i;
/* Shift buffer */ /* Shift buffer */
int offset = instr_start - socket->__instructionbuf; int offset = instr_start - parser->__instructionbuf;
memmove(socket->__instructionbuf, instr_start, memmove(parser->__instructionbuf, instr_start,
unparsed_end - instr_start); unparsed_end - instr_start);
/* Update tracking pointers */ /* Update tracking pointers */
unparsed_end -= offset; unparsed_end -= offset;
unparsed_start -= offset; unparsed_start -= offset;
instr_start = socket->__instructionbuf; instr_start = parser->__instructionbuf;
/* Update parsed elements, if any */ /* Update parsed elements, if any */
for (i=0; i<instruction->__elementc; i++) for (i=0; i < parser->__elementc; i++)
instruction->__elementv[i] -= offset; parser->__elementv[i] -= offset;
} }
@ -215,7 +216,7 @@ guac_instruction* guac_instruction_read(guac_socket* socket,
else { else {
guac_error = GUAC_STATUS_NO_MEMORY; guac_error = GUAC_STATUS_NO_MEMORY;
guac_error_message = "Instruction too long"; 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 ... */ /* No instruction yet? Get more data ... */
retval = guac_socket_select(socket, usec_timeout); retval = guac_socket_select(socket, usec_timeout);
if (retval <= 0) if (retval <= 0)
return NULL; return -1;
/* Attempt to fill buffer */ /* Attempt to fill buffer */
retval = guac_socket_read(socket, unparsed_end, retval = guac_socket_read(socket, unparsed_end,
@ -233,7 +234,7 @@ guac_instruction* guac_instruction_read(guac_socket* socket,
if (retval < 0) { if (retval < 0) {
guac_error = GUAC_STATUS_SEE_ERRNO; guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Error filling instruction buffer"; guac_error_message = "Error filling instruction buffer";
return NULL; return -1;
} }
/* EOF */ /* EOF */
@ -241,7 +242,7 @@ guac_instruction* guac_instruction_read(guac_socket* socket,
guac_error = GUAC_STATUS_CLOSED; guac_error = GUAC_STATUS_CLOSED;
guac_error_message = "End of stream reached while " guac_error_message = "End of stream reached while "
"reading instruction"; "reading instruction";
return NULL; return -1;
} }
/* Update internal buffer */ /* Update internal buffer */
@ -256,55 +257,65 @@ guac_instruction* guac_instruction_read(guac_socket* socket,
} /* end while parsing data */ } /* end while parsing data */
/* Fail on error */ /* Fail on error */
if (instruction->state == GUAC_INSTRUCTION_PARSE_ERROR) { if (parser->state == GUAC_PARSE_ERROR) {
guac_error = GUAC_STATUS_PROTOCOL_ERROR; guac_error = GUAC_STATUS_PROTOCOL_ERROR;
guac_error_message = "Instruction parse error"; guac_error_message = "Instruction parse error";
return NULL; return -1;
} }
socket->__instructionbuf_unparsed_start = unparsed_start; parser->__instructionbuf_unparsed_start = unparsed_start;
socket->__instructionbuf_unparsed_end = unparsed_end; parser->__instructionbuf_unparsed_end = unparsed_end;
return instruction; return 0;
} }
guac_instruction* guac_instruction_expect(guac_socket* socket, int usec_timeout, int guac_parser_expect(guac_parser* parser, guac_socket* socket, int usec_timeout, const char* opcode) {
const char* opcode) {
guac_instruction* instruction; /* Read next instruction */
if (guac_parser_read(parser, socket, usec_timeout) != 0)
/* Wait for data until timeout */ return -1;
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;
/* Validate instruction */ /* Validate instruction */
if (strcmp(instruction->opcode, opcode) != 0) { if (strcmp(parser->opcode, opcode) != 0) {
guac_error = GUAC_STATUS_PROTOCOL_ERROR; guac_error = GUAC_STATUS_PROTOCOL_ERROR;
guac_error_message = "Instruction read did not have expected opcode"; guac_error_message = "Instruction read did not have expected opcode";
guac_instruction_free(instruction); return -1;
return NULL;
} }
/* Return instruction if valid */ /* Return non-zero only if valid instruction read */
return instruction; return parser->state != GUAC_PARSE_COMPLETE;
} }
void guac_instruction_free(guac_instruction* instruction) { int guac_parser_length(guac_parser* parser) {
free(instruction);
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 > char* copy_end = parser->__instructionbuf_unparsed_end;
socket->__instructionbuf_unparsed_start) char* copy_start = parser->__instructionbuf_unparsed_start;
return 1;
/* 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);
} }

View File

@ -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 <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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);
}

View File

@ -28,6 +28,7 @@
guac_pool* guac_pool_alloc(int size) { guac_pool* guac_pool_alloc(int size) {
pthread_mutexattr_t lock_attributes;
guac_pool* pool = malloc(sizeof(guac_pool)); guac_pool* pool = malloc(sizeof(guac_pool));
/* If unable to allocate, just return NULL. */ /* If unable to allocate, just return NULL. */
@ -41,6 +42,11 @@ guac_pool* guac_pool_alloc(int size) {
pool->__head = NULL; pool->__head = NULL;
pool->__tail = 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; return pool;
} }
@ -57,6 +63,9 @@ void guac_pool_free(guac_pool* pool) {
free(old); free(old);
} }
/* Destroy lock */
pthread_mutex_destroy(&(pool->__lock));
/* Free pool */ /* Free pool */
free(pool); free(pool);
@ -66,11 +75,17 @@ int guac_pool_next_int(guac_pool* pool) {
int value; int value;
/* Acquire exclusive access */
pthread_mutex_lock(&(pool->__lock));
pool->active++; pool->active++;
/* If more integers are needed, return a new one. */ /* If more integers are needed, return a new one. */
if (pool->__head == NULL || pool->__next_value < pool->min_size) if (pool->__head == NULL || pool->__next_value < pool->min_size) {
return pool->__next_value++; value = pool->__next_value++;
pthread_mutex_unlock(&(pool->__lock));
return value;
}
/* Otherwise, remove first integer. */ /* Otherwise, remove first integer. */
value = pool->__head->value; value = pool->__head->value;
@ -90,6 +105,7 @@ int guac_pool_next_int(guac_pool* pool) {
} }
/* Return retrieved value. */ /* Return retrieved value. */
pthread_mutex_unlock(&(pool->__lock));
return value; return value;
} }
@ -100,6 +116,9 @@ void guac_pool_free_int(guac_pool* pool, int value) {
pool_int->value = value; pool_int->value = value;
pool_int->__next = NULL; pool_int->__next = NULL;
/* Acquire exclusive access */
pthread_mutex_lock(&(pool->__lock));
pool->active--; pool->active--;
/* If pool empty, store as sole entry. */ /* 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; pool->__tail = pool_int;
} }
/* Value has been freed */
pthread_mutex_unlock(&(pool->__lock));
} }

View File

@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -25,9 +25,11 @@
#include "error.h" #include "error.h"
#include "socket.h" #include "socket.h"
#include <pthread.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h> #include <unistd.h>
@ -37,34 +39,69 @@
#include <sys/select.h> #include <sys/select.h>
#endif #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; 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, /**
void* buf, size_t count) { * The main write buffer. Bytes written go here before being flushed
* to the open file descriptor.
*/
char out_buf[GUAC_SOCKET_OUTPUT_BUFFER_SIZE];
__guac_socket_fd_data* data = (__guac_socket_fd_data*) socket->data; /**
* Lock which is acquired when an instruction is being written, and
* released when the instruction is finished being written.
*/
pthread_mutex_t socket_lock;
/* Read from socket */ /**
int retval = read(data->fd, buf, count); * Lock which protects access to the internal buffer of this socket,
* guaranteeing atomicity of writes and flushes.
*/
pthread_mutex_t buffer_lock;
/* Record errors in guac_error */ } guac_socket_fd_data;
if (retval < 0) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Error reading data from socket";
}
return retval; /**
* 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.
ssize_t __guac_socket_fd_write_handler(guac_socket* socket, *
* @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) { const 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;
const char* buffer = buf;
/* Write until completely written */
while (count > 0) {
int retval; int retval;
#ifdef __MINGW32__ #ifdef __MINGW32__
@ -79,14 +116,171 @@ ssize_t __guac_socket_fd_write_handler(guac_socket* socket,
if (retval < 0) { if (retval < 0) {
guac_error = GUAC_STATUS_SEE_ERRNO; guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Error writing data to socket"; guac_error_message = "Error writing data to socket";
return retval;
}
/* Advance buffer as data retval */
buffer += retval;
count -= retval;
}
return 0;
}
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;
/* Read from socket */
int retval = read(data->fd, buf, count);
/* Record errors in guac_error */
if (retval < 0) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Error reading data from socket";
} }
return retval; return retval;
} }
int __guac_socket_fd_select_handler(guac_socket* socket, int usec_timeout) { /**
* 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; guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
/* Flush remaining bytes in buffer */
if (data->written > 0) {
/* Write ALL bytes in buffer immediately */
if (guac_socket_fd_write(socket, data->out_buf, data->written))
return 1;
data->written = 0;
}
return 0;
}
static ssize_t guac_socket_fd_flush_handler(guac_socket* socket) {
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;
}
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;
}
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; fd_set fds;
struct timeval timeout; struct timeval timeout;
@ -122,20 +316,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) { guac_socket* guac_socket_open(int fd) {
pthread_mutexattr_t lock_attributes;
/* Allocate socket and associated data */ /* Allocate socket and associated data */
guac_socket* socket = guac_socket_alloc(); 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 */ /* Store file descriptor as socket data */
data->fd = fd; data->fd = fd;
data->written = 0;
socket->data = data; 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 */ /* Set read/write handlers */
socket->read_handler = __guac_socket_fd_read_handler; socket->read_handler = guac_socket_fd_read_handler;
socket->write_handler = __guac_socket_fd_write_handler; socket->write_handler = guac_socket_fd_write_handler;
socket->select_handler = __guac_socket_fd_select_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; return socket;

View File

@ -105,6 +105,16 @@ ssize_t __guac_socket_nest_write_handler(guac_socket* socket,
} }
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) { guac_socket* guac_socket_nest(guac_socket* parent, int index) {
/* Allocate socket and associated data */ /* Allocate socket and associated data */
@ -115,8 +125,9 @@ guac_socket* guac_socket_nest(guac_socket* parent, int index) {
data->parent = parent; data->parent = parent;
socket->data = data; socket->data = data;
/* Set write handler */ /* Set write and free handlers */
socket->write_handler = __guac_socket_nest_write_handler; socket->write_handler = __guac_socket_nest_write_handler;
socket->free_handler = __guac_socket_nest_free_handler;
return socket; return socket;

View File

@ -33,6 +33,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
@ -138,7 +139,6 @@ int guac_socket_select(guac_socket* socket, int usec_timeout) {
guac_socket* guac_socket_alloc() { guac_socket* guac_socket_alloc() {
pthread_mutexattr_t lock_attributes;
guac_socket* socket = malloc(sizeof(guac_socket)); guac_socket* socket = malloc(sizeof(guac_socket));
/* If no memory available, return with error */ /* If no memory available, return with error */
@ -149,46 +149,28 @@ guac_socket* guac_socket_alloc() {
} }
socket->__ready = 0; socket->__ready = 0;
socket->__written = 0;
socket->data = NULL; socket->data = NULL;
socket->state = GUAC_SOCKET_OPEN; socket->state = GUAC_SOCKET_OPEN;
socket->last_write_timestamp = guac_timestamp_current(); 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 */ /* No keep alive ping by default */
socket->__keep_alive_enabled = 0; 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 */ /* No handlers yet */
socket->read_handler = NULL; socket->read_handler = NULL;
socket->write_handler = NULL; socket->write_handler = NULL;
socket->select_handler = NULL; socket->select_handler = NULL;
socket->free_handler = NULL; socket->free_handler = NULL;
socket->flush_handler = NULL;
socket->lock_handler = NULL;
socket->unlock_handler = NULL;
return socket; return socket;
} }
void guac_socket_require_threadsafe(guac_socket* socket) {
socket->__threadsafe_instructions = 1;
}
void guac_socket_require_keep_alive(guac_socket* socket) { 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 */ /* Start keep-alive thread */
socket->__keep_alive_enabled = 1; socket->__keep_alive_enabled = 1;
pthread_create(&(socket->__keep_alive_thread), NULL, 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) { void guac_socket_instruction_begin(guac_socket* socket) {
/* Lock writes if threadsafety enabled */ /* Call instruction begin handler if defined */
if (socket->__threadsafe_instructions) if (socket->lock_handler)
pthread_mutex_lock(&(socket->__instruction_write_lock)); socket->lock_handler(socket);
} }
void guac_socket_instruction_end(guac_socket* socket) { void guac_socket_instruction_end(guac_socket* socket) {
/* Unlock writes if threadsafety enabled */ /* Call instruction end handler if defined */
if (socket->__threadsafe_instructions) if (socket->unlock_handler)
pthread_mutex_unlock(&(socket->__instruction_write_lock)); socket->unlock_handler(socket);
}
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));
} }
void guac_socket_free(guac_socket* socket) { void guac_socket_free(guac_socket* socket) {
guac_socket_flush(socket);
/* Call free handler if defined */ /* Call free handler if defined */
if (socket->free_handler) if (socket->free_handler)
socket->free_handler(socket); socket->free_handler(socket);
guac_socket_flush(socket);
/* Mark as closed */ /* Mark as closed */
socket->state = GUAC_SOCKET_CLOSED; socket->state = GUAC_SOCKET_CLOSED;
@ -243,91 +209,92 @@ void guac_socket_free(guac_socket* socket) {
if (socket->__keep_alive_enabled) if (socket->__keep_alive_enabled)
pthread_join(socket->__keep_alive_thread, NULL); pthread_join(socket->__keep_alive_thread, NULL);
pthread_mutex_destroy(&(socket->__instruction_write_lock));
free(socket); free(socket);
} }
ssize_t guac_socket_write_int(guac_socket* socket, int64_t i) { ssize_t guac_socket_write_int(guac_socket* socket, int64_t i) {
char buffer[128]; char buffer[128];
snprintf(buffer, sizeof(buffer), "%"PRIi64, i); int length;
return guac_socket_write_string(socket, buffer);
/* 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) { 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)))
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; return 1;
}
socket->__written = 0;
}
}
guac_socket_update_buffer_end(socket);
return 0; 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 */ /* Byte 0:[AAAAAA] AABBBB BBBBCC CCCCCC */
__out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[(a & 0xFC) >> 2]; /* [AAAAAA]AABBBB BBBBCC CCCCCC */ output[0] = __guac_socket_BASE64_CHARACTERS[(a & 0xFC) >> 2];
if (b >= 0) { 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) { if (c >= 0) {
__out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[((b & 0x0F) << 2) | ((c & 0xC0) >> 6)]; /* AAAAAA AABBBB[BBBBCC]CCCCCC */ output[2] = __guac_socket_BASE64_CHARACTERS[((b & 0x0F) << 2) | ((c & 0xC0) >> 6)];
__out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[c & 0x3F]; /* AAAAAA AABBBB BBBBCC[CCCCCC] */ output[3] = __guac_socket_BASE64_CHARACTERS[c & 0x3F];
} }
/*
* Bytes 2 and 3, one character of padding:
*
* AAAAAA AABBBB [BBBB--] ------
* AAAAAA AABBBB BBBB-- [------]
*/
else { else {
__out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[((b & 0x0F) << 2)]; /* AAAAAA AABBBB[BBBB--]------ */ output[2] = __guac_socket_BASE64_CHARACTERS[((b & 0x0F) << 2)];
__out_buf[socket->__written++] = '='; /* AAAAAA AABBBB BBBB--[------] */ output[3] = '=';
} }
} }
/*
* Bytes 1, 2, and 3, two characters of padding:
*
* AAAAAA [AA----] ------ ------
* AAAAAA AA---- [------] ------
* AAAAAA AA---- ------ [------]
*/
else { else {
__out_buf[socket->__written++] = __guac_socket_BASE64_CHARACTERS[((a & 0x03) << 4)]; /* AAAAAA[AA----]------ ------ */ output[1] = __guac_socket_BASE64_CHARACTERS[((a & 0x03) << 4)];
__out_buf[socket->__written++] = '='; /* AAAAAA AA----[------]------ */ output[2] = '=';
__out_buf[socket->__written++] = '='; /* AAAAAA AA---- ------[------] */ output[3] = '=';
} }
/* At this point, 4 bytes have been socket->__written */ /* At this point, 4 base64 bytes have been written */
if (guac_socket_write(socket, output, 4))
/* 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; return -1;
socket->__written = 0; /* If no second byte was provided, only one byte was written */
}
if (b < 0) if (b < 0)
return 1; return 1;
/* If no third byte was provided, only two bytes were written */
if (c < 0) if (c < 0)
return 2; return 2;
/* Otherwise, three bytes were written */
return 3; 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* char_buf = (const unsigned char*) buf;
const unsigned char* end = char_buf + count; const unsigned char* end = char_buf + count;
guac_socket_update_buffer_begin(socket);
while (char_buf < end) { while (char_buf < end) {
retval = __guac_socket_write_base64_byte(socket, *(char_buf++)); retval = __guac_socket_write_base64_byte(socket, *(char_buf++));
if (retval < 0) { if (retval < 0)
guac_socket_update_buffer_end(socket);
return retval; return retval;
}
} }
guac_socket_update_buffer_end(socket);
return 0; return 0;
} }
ssize_t guac_socket_flush(guac_socket* socket) { ssize_t guac_socket_flush(guac_socket* socket) {
/* Flush remaining bytes in buffer */ /* If handler defined, call it. */
guac_socket_update_buffer_begin(socket); if (socket->flush_handler)
if (socket->__written > 0) { return socket->flush_handler(socket);
if (guac_socket_write(socket, socket->__out_buf, socket->__written)) { /* Otherwise, do nothing */
guac_socket_update_buffer_end(socket);
return 1;
}
socket->__written = 0;
}
guac_socket_update_buffer_end(socket);
return 0; return 0;
} }
@ -399,18 +354,14 @@ ssize_t guac_socket_flush_base64(guac_socket* socket) {
int retval; int retval;
/* Flush triplet to output buffer */ /* Flush triplet to output buffer */
guac_socket_update_buffer_begin(socket);
while (socket->__ready > 0) { while (socket->__ready > 0) {
retval = __guac_socket_write_base64_byte(socket, -1); retval = __guac_socket_write_base64_byte(socket, -1);
if (retval < 0) { if (retval < 0)
guac_socket_update_buffer_end(socket);
return retval; return retval;
}
} }
guac_socket_update_buffer_end(socket);
return 0; return 0;
} }

View File

@ -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);
}

448
src/libguac/user-handlers.c Normal file
View File

@ -0,0 +1,448 @@
/*
* 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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
/* 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_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;
}
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]);
}
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;
}
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;
/* 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 */
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;
}

View File

@ -21,26 +21,26 @@
*/ */
#ifndef _GUAC_CLIENT_HANDLERS__H #ifndef _GUAC_USER_HANDLERS__H
#define _GUAC_CLIENT_HANDLERS__H #define _GUAC_USER_HANDLERS__H
/** /**
* Provides initial handler functions and a lookup structure for automatically * Provides initial handler functions and a lookup structure for automatically
* handling client instructions. This is used only internally within libguac, * handling instructions received from each user. This is used only internally
* and is not installed along with the library. * within libguac, and is not installed along with the library.
* *
* @file client-handlers.h * @file user-handlers.h
*/ */
#include "config.h" #include "config.h"
#include "client.h" #include "client.h"
#include "instruction.h" #include "timestamp.h"
/** /**
* Internal handler for Guacamole instructions. * Internal handler for Guacamole instructions.
*/ */
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. * Structure mapping an instruction opcode to an instruction handler.
@ -64,91 +64,91 @@ typedef struct __guac_instruction_handler_mapping {
* is received, this handler will be called. Sync instructions are automatically * is received, this handler will be called. Sync instructions are automatically
* handled, thus there is no client handler for sync instruction. * handled, thus there is no client handler for sync instruction.
*/ */
int __guac_handle_sync(guac_client* client, guac_instruction* instruction); int __guac_handle_sync(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the mouse instruction. When a mouse instruction * Internal initial handler for the mouse instruction. When a mouse instruction
* is received, this handler will be called. The client's mouse handler will * is received, this handler will be called. The client's mouse handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_mouse(guac_client* client, guac_instruction* instruction); int __guac_handle_mouse(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the key instruction. When a key instruction * Internal initial handler for the key instruction. When a key instruction
* is received, this handler will be called. The client's key handler will * is received, this handler will be called. The client's key handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_key(guac_client* client, guac_instruction* instruction); int __guac_handle_key(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the clipboard instruction. When a clipboard instruction * Internal initial handler for the clipboard instruction. When a clipboard instruction
* is received, this handler will be called. The client's clipboard handler will * is received, this handler will be called. The client's clipboard handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_clipboard(guac_client* client, guac_instruction* instruction); int __guac_handle_clipboard(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the file instruction. When a file instruction * Internal initial handler for the file instruction. When a file instruction
* is received, this handler will be called. The client's file handler will * is received, this handler will be called. The client's file handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_file(guac_client* client, guac_instruction* instruction); int __guac_handle_file(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the pipe instruction. When a pipe instruction * Internal initial handler for the pipe instruction. When a pipe instruction
* is received, this handler will be called. The client's pipe handler will * is received, this handler will be called. The client's pipe handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_pipe(guac_client* client, guac_instruction* instruction); int __guac_handle_pipe(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the ack instruction. When a ack instruction * Internal initial handler for the ack instruction. When a ack instruction
* is received, this handler will be called. The client's ack handler will * is received, this handler will be called. The client's ack handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_ack(guac_client* client, guac_instruction* instruction); int __guac_handle_ack(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the blob instruction. When a blob instruction * Internal initial handler for the blob instruction. When a blob instruction
* is received, this handler will be called. The client's blob handler will * is received, this handler will be called. The client's blob handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_blob(guac_client* client, guac_instruction* instruction); int __guac_handle_blob(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the end instruction. When a end instruction * Internal initial handler for the end instruction. When a end instruction
* is received, this handler will be called. The client's end handler will * is received, this handler will be called. The client's end handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_end(guac_client* client, guac_instruction* instruction); int __guac_handle_end(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the get instruction. When a get instruction * Internal initial handler for the get instruction. When a get instruction
* is received, this handler will be called. The client's get handler will * is received, this handler will be called. The client's get handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_get(guac_client* client, guac_instruction* instruction); int __guac_handle_get(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the put instruction. When a put instruction * Internal initial handler for the put instruction. When a put instruction
* is received, this handler will be called. The client's put handler will * is received, this handler will be called. The client's put handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_put(guac_client* client, guac_instruction* instruction); int __guac_handle_put(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the size instruction. When a size instruction * Internal initial handler for the size instruction. When a size instruction
* is received, this handler will be called. The client's size handler will * is received, this handler will be called. The client's size handler will
* be invoked if defined. * be invoked if defined.
*/ */
int __guac_handle_size(guac_client* client, guac_instruction* instruction); int __guac_handle_size(guac_user* user, int argc, char** argv);
/** /**
* Internal initial handler for the disconnect instruction. When a disconnect instruction * Internal initial handler for the disconnect instruction. When a disconnect instruction
* is received, this handler will be called. Disconnect instructions are automatically * is received, this handler will be called. Disconnect instructions are automatically
* handled, thus there is no client handler for disconnect instruction. * handled, thus there is no client handler for disconnect instruction.
*/ */
int __guac_handle_disconnect(guac_client* client, guac_instruction* instruction); int __guac_handle_disconnect(guac_user* user, int argc, char** argv);
/** /**
* Instruction handler mapping table. This is a NULL-terminated array of * Instruction handler mapping table. This is a NULL-terminated array of

442
src/libguac/user.c Normal file
View File

@ -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 <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
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<GUAC_USER_MAX_STREAMS; i++) {
user->__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<GUAC_USER_MAX_OBJECTS; i++)
user->__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;
}

View File

@ -28,7 +28,6 @@ check_PROGRAMS = test_libguac
noinst_HEADERS = \ noinst_HEADERS = \
client/client_suite.h \ client/client_suite.h \
common/common_suite.h \
protocol/suite.h \ protocol/suite.h \
util/util_suite.h util/util_suite.h
@ -37,10 +36,6 @@ test_libguac_SOURCES = \
client/client_suite.c \ client/client_suite.c \
client/buffer_pool.c \ client/buffer_pool.c \
client/layer_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/suite.c \
protocol/base64_decode.c \ protocol/base64_decode.c \
protocol/instruction_parse.c \ protocol/instruction_parse.c \
@ -53,11 +48,9 @@ test_libguac_SOURCES = \
test_libguac_CFLAGS = \ test_libguac_CFLAGS = \
-Werror -Wall -pedantic \ -Werror -Wall -pedantic \
@COMMON_INCLUDE@ \
@LIBGUAC_INCLUDE@ @LIBGUAC_INCLUDE@
test_libguac_LDADD = \ test_libguac_LDADD = \
@COMMON_LTLIB@ \
@CUNIT_LIBS@ \ @CUNIT_LIBS@ \
@LIBGUAC_LTLIB@ @LIBGUAC_LTLIB@

View File

@ -29,13 +29,13 @@
#include <unistd.h> #include <unistd.h>
#include <CUnit/Basic.h> #include <CUnit/Basic.h>
#include <guacamole/instruction.h> #include <guacamole/parser.h>
void test_instruction_parse() { void test_instruction_parse() {
/* Allocate instruction space */ /* Allocate parser */
guac_instruction* instruction = guac_instruction_alloc(); guac_parser* parser = guac_parser_alloc();
CU_ASSERT_PTR_NOT_NULL_FATAL(instruction); CU_ASSERT_PTR_NOT_NULL_FATAL(parser);
/* Instruction input */ /* Instruction input */
char buffer[] = "4.test,8.testdata,5.zxcvb,13.guacamoletest;XXXXXXXXXXXXXXXXXX"; char buffer[] = "4.test,8.testdata,5.zxcvb,13.guacamoletest;XXXXXXXXXXXXXXXXXX";
@ -46,7 +46,7 @@ void test_instruction_parse() {
while (remaining > 18) { while (remaining > 18) {
/* Parse more data */ /* Parse more data */
int parsed = guac_instruction_append(instruction, current, remaining); int parsed = guac_parser_append(parser, current, remaining);
if (parsed == 0) if (parsed == 0)
break; break;
@ -56,24 +56,24 @@ void test_instruction_parse() {
} }
CU_ASSERT_EQUAL(remaining, 18); 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 */ /* Parse is complete - no more data should be read */
CU_ASSERT_EQUAL(guac_instruction_append(instruction, current, 18), 0); CU_ASSERT_EQUAL(guac_parser_append(parser, current, 18), 0);
CU_ASSERT_EQUAL(instruction->state, GUAC_INSTRUCTION_PARSE_COMPLETE); CU_ASSERT_EQUAL(parser->state, GUAC_PARSE_COMPLETE);
/* Validate resulting structure */ /* Validate resulting structure */
CU_ASSERT_EQUAL(instruction->argc, 3); CU_ASSERT_EQUAL(parser->argc, 3);
CU_ASSERT_PTR_NOT_NULL_FATAL(instruction->opcode); CU_ASSERT_PTR_NOT_NULL_FATAL(parser->opcode);
CU_ASSERT_PTR_NOT_NULL_FATAL(instruction->argv[0]); CU_ASSERT_PTR_NOT_NULL_FATAL(parser->argv[0]);
CU_ASSERT_PTR_NOT_NULL_FATAL(instruction->argv[1]); CU_ASSERT_PTR_NOT_NULL_FATAL(parser->argv[1]);
CU_ASSERT_PTR_NOT_NULL_FATAL(instruction->argv[2]); CU_ASSERT_PTR_NOT_NULL_FATAL(parser->argv[2]);
/* Validate resulting content */ /* Validate resulting content */
CU_ASSERT_STRING_EQUAL(instruction->opcode, "test"); CU_ASSERT_STRING_EQUAL(parser->opcode, "test");
CU_ASSERT_STRING_EQUAL(instruction->argv[0], "testdata"); CU_ASSERT_STRING_EQUAL(parser->argv[0], "testdata");
CU_ASSERT_STRING_EQUAL(instruction->argv[1], "zxcvb"); CU_ASSERT_STRING_EQUAL(parser->argv[1], "zxcvb");
CU_ASSERT_STRING_EQUAL(instruction->argv[2], "guacamoletest"); CU_ASSERT_STRING_EQUAL(parser->argv[2], "guacamoletest");
} }

View File

@ -30,7 +30,7 @@
#include <CUnit/Basic.h> #include <CUnit/Basic.h>
#include <guacamole/error.h> #include <guacamole/error.h>
#include <guacamole/instruction.h> #include <guacamole/parser.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>
@ -71,7 +71,7 @@ void test_instruction_read() {
else { else {
guac_socket* socket; guac_socket* socket;
guac_instruction* instruction; guac_parser* parser;
close(wfd); close(wfd);
@ -79,28 +79,30 @@ void test_instruction_read() {
socket = guac_socket_open(rfd); socket = guac_socket_open(rfd);
CU_ASSERT_PTR_NOT_NULL_FATAL(socket); CU_ASSERT_PTR_NOT_NULL_FATAL(socket);
/* Allocate parser */
parser = guac_parser_alloc();
CU_ASSERT_PTR_NOT_NULL_FATAL(parser);
/* Read instruction */ /* Read instruction */
instruction = guac_instruction_read(socket, 1000000); CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0);
CU_ASSERT_PTR_NOT_NULL_FATAL(instruction);
/* Validate contents */ /* Validate contents */
CU_ASSERT_STRING_EQUAL(instruction->opcode, "test"); CU_ASSERT_STRING_EQUAL(parser->opcode, "test");
CU_ASSERT_EQUAL_FATAL(instruction->argc, 3); CU_ASSERT_EQUAL_FATAL(parser->argc, 3);
CU_ASSERT_STRING_EQUAL(instruction->argv[0], "a" UTF8_4 "b"); CU_ASSERT_STRING_EQUAL(parser->argv[0], "a" UTF8_4 "b");
CU_ASSERT_STRING_EQUAL(instruction->argv[1], "12345"); CU_ASSERT_STRING_EQUAL(parser->argv[1], "12345");
CU_ASSERT_STRING_EQUAL(instruction->argv[2], "a" UTF8_8 "c"); CU_ASSERT_STRING_EQUAL(parser->argv[2], "a" UTF8_8 "c");
/* Read another instruction */ /* Read another instruction */
guac_instruction_free(instruction); CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0);
instruction = guac_instruction_read(socket, 1000000);
/* Validate contents */ /* Validate contents */
CU_ASSERT_STRING_EQUAL(instruction->opcode, "test2"); CU_ASSERT_STRING_EQUAL(parser->opcode, "test2");
CU_ASSERT_EQUAL_FATAL(instruction->argc, 2); CU_ASSERT_EQUAL_FATAL(parser->argc, 2);
CU_ASSERT_STRING_EQUAL(instruction->argv[0], "hellohello"); CU_ASSERT_STRING_EQUAL(parser->argv[0], "hellohello");
CU_ASSERT_STRING_EQUAL(instruction->argv[1], "worldworldworld"); CU_ASSERT_STRING_EQUAL(parser->argv[1], "worldworldworld");
guac_instruction_free(instruction); guac_parser_free(parser);
guac_socket_free(socket); guac_socket_free(socket);
} }

View File

@ -30,7 +30,6 @@
#include <CUnit/Basic.h> #include <CUnit/Basic.h>
#include <guacamole/error.h> #include <guacamole/error.h>
#include <guacamole/instruction.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>

View File

@ -30,7 +30,6 @@
#include <CUnit/Basic.h> #include <CUnit/Basic.h>
#include <guacamole/error.h> #include <guacamole/error.h>
#include <guacamole/instruction.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>

View File

@ -39,7 +39,6 @@ int main() {
register_protocol_suite(); register_protocol_suite();
register_client_suite(); register_client_suite();
register_util_suite(); register_util_suite();
register_common_suite();
/* Run tests */ /* Run tests */
CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_set_mode(CU_BRM_VERBOSE);