Merge pull request #109 from glyptodon/fake-merge-screen-sharing-001-libguac

GUAC-1389: Refactor libguac to allow screen sharing.
This commit is contained in:
James Muehlner 2016-03-01 16:23:44 -08:00
commit 03f9cde27c
45 changed files with 4419 additions and 1893 deletions

View File

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

View File

@ -38,16 +38,15 @@ libguacinc_HEADERS = \
guacamole/error.h \
guacamole/error-types.h \
guacamole/hash.h \
guacamole/instruction-constants.h \
guacamole/instruction.h \
guacamole/instruction-types.h \
guacamole/layer.h \
guacamole/layer-types.h \
guacamole/object.h \
guacamole/object-types.h \
guacamole/parser-constants.h \
guacamole/parser.h \
guacamole/parser-types.h \
guacamole/plugin-constants.h \
guacamole/plugin.h \
guacamole/plugin-types.h \
guacamole/pool.h \
guacamole/pool-types.h \
guacamole/protocol.h \
@ -60,26 +59,30 @@ libguacinc_HEADERS = \
guacamole/stream-types.h \
guacamole/timestamp.h \
guacamole/timestamp-types.h \
guacamole/unicode.h
guacamole/unicode.h \
guacamole/user.h \
guacamole/user-constants.h \
guacamole/user-fntypes.h \
guacamole/user-types.h
noinst_HEADERS = \
client-handlers.h \
id.h \
encode-jpeg.h \
encode-png.h \
palette.h \
user-handlers.h \
raw_encoder.h
libguac_la_SOURCES = \
audio.c \
client.c \
client-handlers.c \
encode-jpeg.c \
encode-png.c \
error.c \
hash.c \
instruction.c \
id.c \
palette.c \
plugin.c \
parser.c \
pool.c \
protocol.c \
raw_encoder.c \
@ -87,7 +90,9 @@ libguac_la_SOURCES = \
socket-fd.c \
socket-nest.c \
timestamp.c \
unicode.c
unicode.c \
user.c \
user-handlers.c
# Compile WebP support if available
if ENABLE_WEBP

View File

@ -28,51 +28,74 @@
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <stdlib.h>
#include <string.h>
/**
* Assigns a new audio encoder to the given guac_audio_stream based on the
* audio mimetypes declared as supported by the given user. If no audio encoder
* can be found, no new audio encoder is assigned, and the existing encoder is
* left untouched (if any).
*
* @param owner
* The user whose supported audio mimetypes should determine the audio
* encoder selected. It is expected that this user will be the owner of
* the connection.
*
* @param data
* The guac_audio_stream to which the new encoder should be assigned.
* Existing properties set on this audio stream, such as the bits per
* sample, may affect the encoder chosen.
*
* @return
* The assigned audio encoder. If no new audio encoder can be assigned,
* this will be the currently-assigned audio encoder (which may be NULL).
*/
static void* guac_audio_assign_encoder(guac_user* owner, void* data) {
int i;
guac_audio_stream* audio = (guac_audio_stream*) data;
int bps = audio->bps;
/* If there is no owner, do not attempt to assign a new encoder */
if (owner == NULL)
return audio->encoder;
/* For each supported mimetype, check for an associated encoder */
for (i=0; owner->info.audio_mimetypes[i] != NULL; i++) {
const char* mimetype = owner->info.audio_mimetypes[i];
/* If 16-bit raw audio is supported, done. */
if (bps == 16 && strcmp(mimetype, raw16_encoder->mimetype) == 0) {
audio->encoder = raw16_encoder;
break;
}
/* If 8-bit raw audio is supported, done. */
if (bps == 8 && strcmp(mimetype, raw8_encoder->mimetype) == 0) {
audio->encoder = raw8_encoder;
break;
}
} /* end for each mimetype */
/* Return assigned encoder, if any */
return audio->encoder;
}
guac_audio_stream* guac_audio_stream_alloc(guac_client* client,
guac_audio_encoder* encoder, int rate, int channels, int bps) {
guac_audio_stream* audio;
/* Choose an encoding if not specified */
if (encoder == NULL) {
int i;
/* For each supported mimetype, check for an associated encoder */
for (i=0; client->info.audio_mimetypes[i] != NULL; i++) {
const char* mimetype = client->info.audio_mimetypes[i];
/* If 16-bit raw audio is supported, done. */
if (bps == 16 && strcmp(mimetype, raw16_encoder->mimetype) == 0) {
encoder = raw16_encoder;
break;
}
/* If 8-bit raw audio is supported, done. */
if (bps == 8 && strcmp(mimetype, raw8_encoder->mimetype) == 0) {
encoder = raw8_encoder;
break;
}
} /* end for each mimetype */
/* If still no encoder could be found, fail */
if (encoder == NULL)
return NULL;
}
/* Allocate stream */
audio = (guac_audio_stream*) calloc(1, sizeof(guac_audio_stream));
audio->client = client;
/* Assign encoder */
audio->encoder = encoder;
audio->stream = guac_client_alloc_stream(client);
/* Load PCM properties */
@ -80,6 +103,13 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client,
audio->channels = channels;
audio->bps = bps;
/* Assign encoder for owner, abort if no encoder can be found */
if (!guac_client_for_owner(client, guac_audio_assign_encoder, audio)) {
guac_client_free_stream(client, audio->stream);
free(audio);
return NULL;
}
/* Call handler, if defined */
if (audio->encoder->begin_handler)
audio->encoder->begin_handler(audio);
@ -118,6 +148,16 @@ void guac_audio_stream_reset(guac_audio_stream* audio,
}
void guac_audio_stream_add_user(guac_audio_stream* audio, guac_user* user) {
guac_audio_encoder* encoder = audio->encoder;
/* Notify encoder that a new user is present */
if (encoder->join_handler)
encoder->join_handler(audio, user);
}
void guac_audio_stream_free(guac_audio_stream* audio) {
/* Flush stream encoding */

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 "client.h"
#include "client-handlers.h"
#include "encode-jpeg.h"
#include "encode-png.h"
#include "encode-webp.h"
#include "error.h"
#include "instruction.h"
#include "id.h"
#include "layer.h"
#include "object.h"
#include "pool.h"
#include "plugin.h"
#include "protocol.h"
#include "socket.h"
#include "stream.h"
#include "timestamp.h"
#include "user.h"
#ifdef ENABLE_WEBP
#include "encode-webp.h"
#endif
#ifdef HAVE_OSSP_UUID_H
#include <ossp/uuid.h>
#else
#include <uuid.h>
#endif
#include <dlfcn.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@ -57,6 +50,23 @@ guac_layer __GUAC_DEFAULT_LAYER = {
const guac_layer* GUAC_DEFAULT_LAYER = &__GUAC_DEFAULT_LAYER;
/**
* Single chunk of data, to be broadcast to all users.
*/
typedef struct __write_chunk {
/**
* The buffer to write.
*/
const void* buffer;
/**
* The number of bytes in the buffer.
*/
size_t length;
} __write_chunk;
guac_layer* guac_client_alloc_layer(guac_client* client) {
/* Init new layer */
@ -109,9 +119,9 @@ guac_stream* guac_client_alloc_stream(guac_client* client) {
/* Allocate stream */
stream_index = guac_pool_next_int(client->__stream_pool);
/* Initialize stream */
/* Initialize stream with odd index (even indices are user-level) */
allocd_stream = &(client->__output_streams[stream_index]);
allocd_stream->index = stream_index;
allocd_stream->index = (stream_index * 2) + 1;
allocd_stream->data = NULL;
allocd_stream->ack_handler = NULL;
allocd_stream->blob_handler = NULL;
@ -124,107 +134,262 @@ guac_stream* guac_client_alloc_stream(guac_client* client) {
void guac_client_free_stream(guac_client* client, guac_stream* stream) {
/* Release index to pool */
guac_pool_free_int(client->__stream_pool, stream->index);
guac_pool_free_int(client->__stream_pool, (stream->index - 1) / 2);
/* Mark stream as closed */
stream->index = GUAC_CLIENT_CLOSED_STREAM_INDEX;
}
guac_object* guac_client_alloc_object(guac_client* client) {
/**
* Callback which handles read requests on the broadcast socket. This callback
* always fails, as the broadcast socket is write-only; it cannot be read.
*
* @param socket
* The broadcast socket to read from.
*
* @param buf
* The buffer into which data should be read.
*
* @param count
* The number of bytes to attempt to read.
*
* @return
* The number of bytes read, or -1 if an error occurs. This implementation
* always returns -1, as the broadcast socket is write-only and cannot be
* read.
*/
static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket,
void* buf, size_t count) {
guac_object* allocd_object;
int object_index;
/* Refuse to allocate beyond maximum */
if (client->__object_pool->active == GUAC_CLIENT_MAX_OBJECTS)
return NULL;
/* Allocate object */
object_index = guac_pool_next_int(client->__object_pool);
/* Initialize object */
allocd_object = &(client->__objects[object_index]);
allocd_object->index = object_index;
allocd_object->data = NULL;
allocd_object->get_handler = NULL;
allocd_object->put_handler = NULL;
return allocd_object;
}
void guac_client_free_object(guac_client* client, guac_object* object) {
/* Release index to pool */
guac_pool_free_int(client->__object_pool, object->index);
/* Mark object as undefined */
object->index = GUAC_CLIENT_UNDEFINED_OBJECT_INDEX;
/* Broadcast socket reads are not allowed */
return -1;
}
/**
* Returns a newly allocated string containing a guaranteed-unique connection
* identifier string which is 37 characters long and begins with a '$'
* character. If an error occurs, NULL is returned, and no memory is
* allocated.
* Callback invoked by guac_client_foreach_user() which write a given chunk of
* data to that user's socket. If the write attempt fails, the user is
* signalled to stop with guac_user_stop().
*
* @param user
* The user that the chunk of data should be written to.
*
* @param data
* A pointer to a __write_chunk which describes the data to be written.
*
* @return
* Always NULL.
*/
static char* __guac_generate_connection_id() {
static void* __write_chunk_callback(guac_user* user, void* data) {
char* buffer;
char* identifier;
size_t identifier_length;
__write_chunk* chunk = (__write_chunk*) data;
uuid_t* uuid;
/* Attempt write, disconnect on failure */
if (guac_socket_write(user->socket, chunk->buffer, chunk->length))
guac_user_stop(user);
/* Attempt to create UUID object */
if (uuid_create(&uuid) != UUID_RC_OK) {
guac_error = GUAC_STATUS_NO_MEMORY;
guac_error_message = "Could not allocate memory for UUID";
return NULL;
}
/* 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";
}
/**
* Socket write handler which operates on each of the sockets of all connected
* users. This write handler will always succeed, but any failing user-specific
* writes will invoke guac_user_stop() on the failing user.
*
* @param socket
* The socket to which the given data must be written.
*
* @param buf
* The buffer containing the data to write.
*
* @param count
* The number of bytes to attempt to write from the given buffer.
*
* @return
* The number of bytes written, or -1 if an error occurs. This handler will
* always succeed, and thus will always return the exact number of bytes
* specified by count.
*/
static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket,
const void* buf, size_t count) {
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;
}
/**
* Callback which is invoked by guac_client_foreach_user() to flush all
* pending data on the given user's socket. If an error occurs while flushing
* a user's socket, that user is signalled to stop with guac_user_stop().
*
* @param user
* The user whose socket should be flushed.
*
* @param data
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
* by this callback, and should be left as NULL.
*
* @return
* Always NULL.
*/
static void* __flush_callback(guac_user* user, void* data) {
/* Attempt flush, disconnect on failure */
if (guac_socket_flush(user->socket))
guac_user_stop(user);
return NULL;
}
/* 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";
}
/**
* Socket flush handler which operates on each of the sockets of all connected
* users. This flush handler will always succeed, but any failing user-specific
* flush will invoke guac_user_stop() on the failing user.
*
* @param socket
* The broadcast socket to flush.
*
* @return
* Zero if the flush operation succeeds, non-zero if the operation fails.
* This handler will always succeed, and thus will always return zero.
*/
static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) {
guac_client* client = (guac_client*) socket->data;
/* Flush all users */
guac_client_foreach_user(client, __flush_callback, NULL);
return 0;
}
/**
* Callback which is invoked by guac_client_foreach_user() to lock the given
* user's socket in preparation for the beginning of a Guacamole protocol
* instruction.
*
* @param user
* The user whose socket should be locked.
*
* @param data
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
* by this callback, and should be left as NULL.
*
* @return
* Always NULL.
*/
static void* __lock_callback(guac_user* user, void* data) {
/* Lock socket */
guac_socket_instruction_begin(user->socket);
return NULL;
}
identifier = &(buffer[1]);
identifier_length = UUID_LEN_STR + 1;
}
/**
* Socket lock handler which acquires the socket locks of all connected users.
* Socket-level locks are acquired in preparation for the beginning of a new
* Guacamole instruction to ensure that parallel writes are only interleaved at
* instruction boundaries.
*
* @param socket
* The broadcast socket to lock.
*/
static void __guac_socket_broadcast_lock_handler(guac_socket* socket) {
guac_client* client = (guac_client*) socket->data;
/* Lock sockets of all users */
guac_client_foreach_user(client, __lock_callback, NULL);
}
/**
* Callback which is invoked by guac_client_foreach_user() to unlock the given
* user's socket at the end of a Guacamole protocol instruction.
*
* @param user
* The user whose socket should be unlocked.
*
* @param data
* Arbitrary data passed to guac_client_foreach_user(). This is not needed
* by this callback, and should be left as NULL.
*
* @return
* Always NULL.
*/
static void* __unlock_callback(guac_user* user, void* data) {
/* Unlock socket */
guac_socket_instruction_end(user->socket);
/* 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] = '$';
buffer[UUID_LEN_STR + 1] = '\0';
return buffer;
/**
* Socket unlock handler which releases the socket locks of all connected users.
* Socket-level locks are released after a Guacamole instruction has finished
* being written.
*
* @param socket
* The broadcast socket to unlock.
*/
static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) {
guac_client* client = (guac_client*) socket->data;
/* Unlock sockets of all users */
guac_client_foreach_user(client, __unlock_callback, NULL);
}
/**
* Callback which handles select operations on the broadcast socket, waiting
* for data to become available such that the next read operation will not
* block. This callback always fails, as the broadcast socket is write-only; it
* cannot be read.
*
* @param socket
* The broadcast socket to wait for.
*
* @param usec_timeout
* The maximum amount of time to wait for data, in microseconds, or -1 to
* potentially wait forever.
*
* @return
* A positive value on success, zero if the timeout elapsed and no data is
* available, or a negative value if an error occurs. This implementation
* always returns -1, as the broadcast socket is write-only and cannot be
* read.
*/
static int __guac_socket_broadcast_select_handler(guac_socket* socket,
int usec_timeout) {
/* Selecting the broadcast socket is not possible */
return -1;
}
guac_client* guac_client_alloc() {
int i;
pthread_rwlockattr_t lock_attributes;
/* Allocate new client */
guac_client* client = malloc(sizeof(guac_client));
@ -237,13 +402,11 @@ guac_client* guac_client_alloc() {
/* Init new client */
memset(client, 0, sizeof(guac_client));
client->last_received_timestamp =
client->state = GUAC_CLIENT_RUNNING;
client->last_sent_timestamp = guac_timestamp_current();
client->state = GUAC_CLIENT_RUNNING;
/* Generate ID */
client->connection_id = __guac_generate_connection_id();
client->connection_id = guac_generate_id(GUAC_CLIENT_ID_PREFIX);
if (client->connection_id == NULL) {
free(client);
return NULL;
@ -257,21 +420,30 @@ guac_client* guac_client_alloc() {
client->__stream_pool = guac_pool_alloc(0);
/* Initialize streams */
client->__input_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS);
client->__output_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS);
for (i=0; i<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;
}
/* Allocate object pool */
client->__object_pool = guac_pool_alloc(0);
/* Initialize objects */
client->__objects = malloc(sizeof(guac_object) * GUAC_CLIENT_MAX_OBJECTS);
for (i=0; i<GUAC_CLIENT_MAX_OBJECTS; i++)
client->__objects[i].index = GUAC_CLIENT_UNDEFINED_OBJECT_INDEX;
/* Init locks */
pthread_rwlockattr_init(&lock_attributes);
pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
pthread_rwlock_init(&(client->__users_lock), &lock_attributes);
/* Set up socket to broadcast to all users */
guac_socket* socket = guac_socket_alloc();
client->socket = socket;
socket->data = client;
socket->read_handler = __guac_socket_broadcast_read_handler;
socket->write_handler = __guac_socket_broadcast_write_handler;
socket->select_handler = __guac_socket_broadcast_select_handler;
socket->flush_handler = __guac_socket_broadcast_flush_handler;
socket->lock_handler = __guac_socket_broadcast_lock_handler;
socket->unlock_handler = __guac_socket_broadcast_unlock_handler;
return client;
@ -279,6 +451,10 @@ guac_client* guac_client_alloc() {
void guac_client_free(guac_client* client) {
/* Remove all users */
while (client->__users != NULL)
guac_client_remove_user(client, client->__users);
if (client->free_handler) {
/* FIXME: Errors currently ignored... */
@ -291,37 +467,19 @@ void guac_client_free(guac_client* client) {
guac_pool_free(client->__layer_pool);
/* Free streams */
free(client->__input_streams);
free(client->__output_streams);
/* Free stream pool */
guac_pool_free(client->__stream_pool);
/* Free objects */
free(client->__objects);
/* Free object pool */
guac_pool_free(client->__object_pool);
free(client);
}
int guac_client_handle_instruction(guac_client* client, guac_instruction* instruction) {
/* For each defined instruction */
__guac_instruction_handler_mapping* current = __guac_instruction_handler_map;
while (current->opcode != NULL) {
/* If recognized, call handler */
if (strcmp(instruction->opcode, current->opcode) == 0)
return current->handler(client, instruction);
current++;
/* Close associated plugin */
if (client->__plugin_handle != NULL) {
if (dlclose(client->__plugin_handle))
guac_client_log(client, GUAC_LOG_ERROR, "Unable to close plugin: %s", dlerror());
}
/* If unrecognized, ignore */
return 0;
pthread_rwlock_destroy(&(client->__users_lock));
free(client);
}
void vguac_client_log(guac_client* client, guac_client_log_level level,
@ -381,6 +539,203 @@ void guac_client_abort(guac_client* client, guac_protocol_status status,
}
int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** argv) {
int retval = 0;
pthread_rwlock_wrlock(&(client->__users_lock));
/* Call handler, if defined */
if (client->join_handler)
retval = client->join_handler(user, argc, argv);
/* Add to list if join was successful */
if (retval == 0) {
user->__prev = NULL;
user->__next = client->__users;
if (client->__users != NULL)
client->__users->__prev = user;
client->__users = user;
client->connected_users++;
/* Update owner pointer if user is owner */
if (user->owner)
client->__owner = user;
}
pthread_rwlock_unlock(&(client->__users_lock));
return retval;
}
void guac_client_remove_user(guac_client* client, guac_user* user) {
pthread_rwlock_wrlock(&(client->__users_lock));
/* Call handler, if defined */
if (user->leave_handler)
user->leave_handler(user);
else if (client->leave_handler)
client->leave_handler(user);
/* Update prev / head */
if (user->__prev != NULL)
user->__prev->__next = user->__next;
else
client->__users = user->__next;
/* Update next */
if (user->__next != NULL)
user->__next->__prev = user->__prev;
client->connected_users--;
/* Update owner pointer if user was owner */
if (user->owner)
client->__owner = NULL;
pthread_rwlock_unlock(&(client->__users_lock));
}
void guac_client_foreach_user(guac_client* client, guac_user_callback* callback, void* data) {
guac_user* current;
pthread_rwlock_rdlock(&(client->__users_lock));
/* Call function on each user */
current = client->__users;
while (current != NULL) {
callback(current, data);
current = current->__next;
}
pthread_rwlock_unlock(&(client->__users_lock));
}
void* guac_client_for_owner(guac_client* client, guac_user_callback* callback,
void* data) {
void* retval;
pthread_rwlock_rdlock(&(client->__users_lock));
/* Invoke callback with current owner */
retval = callback(client->__owner, data);
pthread_rwlock_unlock(&(client->__users_lock));
/* Return value from callback */
return retval;
}
int guac_client_end_frame(guac_client* client) {
/* Update and send timestamp */
client->last_sent_timestamp = guac_timestamp_current();
return guac_protocol_send_sync(client->socket, client->last_sent_timestamp);
}
/**
* Empty NULL-terminated array of argument names.
*/
const char* __GUAC_CLIENT_NO_ARGS[] = { NULL };
int guac_client_load_plugin(guac_client* client, const char* protocol) {
/* Reference to dlopen()'d plugin */
void* client_plugin_handle;
/* Pluggable client */
char protocol_lib[GUAC_PROTOCOL_LIBRARY_LIMIT] =
GUAC_PROTOCOL_LIBRARY_PREFIX;
/* Type-pun for the sake of dlsym() - cannot typecast a void* to a function
* pointer otherwise */
union {
guac_client_init_handler* client_init;
void* obj;
} alias;
/* Add protocol and .so suffix to protocol_lib */
strncat(protocol_lib, protocol, GUAC_PROTOCOL_NAME_LIMIT-1);
strcat(protocol_lib, GUAC_PROTOCOL_LIBRARY_SUFFIX);
/* Load client plugin */
client_plugin_handle = dlopen(protocol_lib, RTLD_LAZY);
if (!client_plugin_handle) {
guac_error = GUAC_STATUS_NOT_FOUND;
guac_error_message = dlerror();
return -1;
}
dlerror(); /* Clear errors */
/* Get init function */
alias.obj = dlsym(client_plugin_handle, "guac_client_init");
/* Fail if cannot find guac_client_init */
if (dlerror() != NULL) {
guac_error = GUAC_STATUS_INTERNAL_ERROR;
guac_error_message = dlerror();
return -1;
}
/* Init client */
client->args = __GUAC_CLIENT_NO_ARGS;
client->__plugin_handle = client_plugin_handle;
return alias.client_init(client);
}
/**
* Updates the provided approximate processing lag, taking into account the
* processing lag of the given user.
*
* @param user
* The guac_user to use to update the approximate processing lag.
*
* @param data
* Pointer to an int containing the current approximate processing lag.
* The int will be updated according to the processing lag of the given
* user.
*
* @return
* Always NULL.
*/
static void* __calculate_lag(guac_user* user, void* data) {
int* processing_lag = (int*) data;
/* Simply find maximum */
if (user->processing_lag > *processing_lag)
*processing_lag = user->processing_lag;
return NULL;
}
int guac_client_get_processing_lag(guac_client* client) {
int processing_lag = 0;
/* Approximate the processing lag of all users */
guac_client_foreach_user(client, __calculate_lag, &processing_lag);
return processing_lag;
}
void guac_client_stream_png(guac_client* client, guac_socket* socket,
guac_composite_mode mode, const guac_layer* layer, int x, int y,
cairo_surface_t* surface) {
@ -448,25 +803,45 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket,
}
#ifdef ENABLE_WEBP
/**
* Callback which is invoked by guac_client_supports_webp() for each user
* associated with the given client, thus updating an overall support flag
* describing the WebP support state for the client as a whole.
*
* @param user
* The user to check for WebP support.
*
* @param data
* Pointer to an int containing the current WebP support status for the
* client associated with the given user. This flag will be 0 if any user
* already checked has lacked WebP support, or 1 otherwise.
*
* @return
* Always NULL.
*/
static void* __webp_support_callback(guac_user* user, void* data) {
int* webp_supported = (int*) data;
/* Check whether current user supports WebP */
if (*webp_supported)
*webp_supported = guac_user_supports_webp(user);
return NULL;
}
#endif
int guac_client_supports_webp(guac_client* client) {
#ifdef ENABLE_WEBP
char** mimetype = client->info.image_mimetypes;
int webp_supported = 1;
/* Search for WebP mimetype in list of supported image mimetypes */
while (*mimetype != NULL) {
/* WebP is supported for entire client only if each user supports it */
guac_client_foreach_user(client, __webp_support_callback, &webp_supported);
/* If WebP mimetype found, no need to search further */
if (strcmp(*mimetype, "image/webp") == 0)
return 1;
/* Next mimetype */
mimetype++;
}
/* Client does not support WebP */
return 0;
return webp_supported;
#else
/* Support for WebP is completely absent */
return 0;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2015 Glyptodon LLC
* Copyright (C) 2016 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -20,8 +20,8 @@
* THE SOFTWARE.
*/
#ifndef __GUAC_AUDIO_FNTYPES_H
#define __GUAC_AUDIO_FNTYPES_H
#ifndef GUAC_AUDIO_FNTYPES_H
#define GUAC_AUDIO_FNTYPES_H
/**
* Function type definitions related to simple streaming audio.
@ -30,24 +30,59 @@
*/
#include "audio-types.h"
#include "user-types.h"
/**
* Handler which is called when the audio stream is opened.
*
* @param audio
* The audio stream being opened.
*/
typedef void guac_audio_encoder_begin_handler(guac_audio_stream* audio);
/**
* Handler which is called when the audio stream needs to be flushed.
*
* @param audio
* The audio stream being flushed.
*/
typedef void guac_audio_encoder_flush_handler(guac_audio_stream* audio);
/**
* Handler which is called when the audio stream is closed.
*
* @param audio
* The audio stream being closed.
*/
typedef void guac_audio_encoder_end_handler(guac_audio_stream* audio);
/**
* Handler which is called when PCM data is written to the audio stream.
* Handler which is called when a new user has joined the Guacamole
* connection associated with the audio stream.
*
* @param audio
* The audio stream associated with the Guacamole connection being
* joined.
*
* @param user
* The user that joined the connection.
*/
typedef void guac_audio_encoder_join_handler(guac_audio_stream* audio,
guac_user* user);
/**
* Handler which is called when PCM data is written to the audio stream. The
* format of the PCM data is dictated by the properties of the audio stream.
*
* @param audio
* The audio stream to which data is being written.
*
* @param pcm_data
* A buffer containing the raw PCM data to be written.
*
* @param length
* The number of bytes within the buffer that should be written to the
* audio stream.
*/
typedef void guac_audio_encoder_write_handler(guac_audio_stream* audio,
const unsigned char* pcm_data, int length);

View File

@ -64,6 +64,12 @@ struct guac_audio_encoder {
*/
guac_audio_encoder_end_handler* end_handler;
/**
* Handler which will be called when a new user joins the Guacamole
* connection associated with an audio stream.
*/
guac_audio_encoder_join_handler* join_handler;
};
struct guac_audio_stream {
@ -108,15 +114,24 @@ struct guac_audio_stream {
};
/**
* Allocates a new audio stream which encodes audio data using the given
* encoder. If NULL is specified for the encoder, an appropriate encoder
* will be selected based on the encoders built into libguac and the level
* of client support. The PCM format specified here (via rate, channels, and
* Allocates a new audio stream at the client level which encodes audio data
* using the given encoder. If NULL is specified for the encoder, an
* appropriate encoder will be selected based on the encoders built into
* libguac and the level of support declared by the owner associated with the
* given guac_client. The PCM format specified here (via rate, channels, and
* bps) must be the format used for all PCM data provided to the audio stream.
* The format may only be changed using guac_audio_stream_reset().
*
* If a new user joins the connection after the audio stream is created, that
* user will not be aware of the existence of the audio stream, and
* guac_audio_stream_add_user() will need to be invoked to recreate the stream
* for the new user.
*
* @param client
* The guac_client for which this audio stream is being allocated.
* The guac_client for which this audio stream is being allocated. Only the
* connection owner is used to determine the level of audio support, and it
* is currently assumed that all other joining users on the connection will
* have the same level of audio support.
*
* @param encoder
* The guac_audio_encoder to use when encoding audio, or NULL if libguac
@ -135,7 +150,8 @@ struct guac_audio_stream {
*
* @return
* The newly allocated guac_audio_stream, or NULL if no audio stream could
* be allocated due to lack of client support.
* be allocated due to lack of support on the part of the connecting
* Guacamole client.
*/
guac_audio_stream* guac_audio_stream_alloc(guac_client* client,
guac_audio_encoder* encoder, int rate, int channels, int bps);
@ -168,6 +184,21 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client,
void guac_audio_stream_reset(guac_audio_stream* audio,
guac_audio_encoder* encoder, int rate, int channels, int bps);
/**
* Notifies the given audio stream that a user has joined the connection. The
* audio stream itself may need to be restarted. and the audio stream will need
* to be created for the new user to ensure they can properly handle future
* data received along the stream.
*
* @param audio
* The guac_audio_stream associated with the Guacamole connection being
* joined.
*
* @param user
* The user that has joined the Guacamole connection.
*/
void guac_audio_stream_add_user(guac_audio_stream* audio, guac_user* user);
/**
* Closes and frees the given audio stream.
*

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
@ -40,26 +41,9 @@
#define GUAC_CLIENT_CLOSED_STREAM_INDEX -1
/**
* The maximum number of objects supported by any one guac_client.
* The character prefix which identifies a client ID.
*/
#define GUAC_CLIENT_MAX_OBJECTS 64
/**
* The index of an object which has not been defined.
*/
#define GUAC_CLIENT_UNDEFINED_OBJECT_INDEX -1
/**
* The stream name reserved for the root of a Guacamole protocol object.
*/
#define GUAC_CLIENT_OBJECT_ROOT_NAME "/"
/**
* The mimetype of a stream containing a map of available stream names to their
* corresponding mimetypes. The root of a Guacamole protocol object is
* guaranteed to have this type.
*/
#define GUAC_CLIENT_STREAM_INDEX_MIMETYPE "application/vnd.glyptodon.guacamole.stream-index+json"
#define GUAC_CLIENT_ID_PREFIX '$'
/**
* The flag set in the mouse button mask when the left mouse button is down.

View File

@ -34,102 +34,53 @@
#include "object-types.h"
#include "protocol-types.h"
#include "stream-types.h"
#include "user-types.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
* implementation.
*
* @param client
* The client whose extra data should be freed (if any).
*
* @return
* Zero if the data was successfully freed, non-zero if an error prevents
* the data from being freed.
*/
typedef int guac_client_free_handler(guac_client* client);
/**
* Handler for logging messages
* Handler for logging messages related to a given guac_client instance.
*
* @param client
* The client related to the message being logged.
*
* @param level
* The log level at which to log the given message.
*
* @param format
* A printf-style format string, defining the message to be logged.
*
* @param args
* The va_list containing the arguments to be used when filling the
* conversion specifiers ("%s", "%i", etc.) within the format string.
*/
typedef void guac_client_log_handler(guac_client* client, guac_client_log_level level, const char* format, va_list args);
typedef void guac_client_log_handler(guac_client* client,
guac_client_log_level level, const char* format, va_list args);
/**
* Handler which should initialize the given guac_client.
* The entry point of a client plugin which must initialize the given
* guac_client. In practice, this function will be called "guac_client_init".
*
* @param client
* The guac_client that must be initialized.
*
* @return
* Zero on success, non-zero if initialization fails for any reason.
*/
typedef int guac_client_init_handler(guac_client* client, int argc, char** argv);
typedef int guac_client_init_handler(guac_client* client);
#endif

View File

@ -87,11 +87,5 @@ typedef enum guac_client_log_level {
} guac_client_log_level;
/**
* Information exposed by the remote client during the connection handshake
* which can be used by a client plugin.
*/
typedef struct guac_client_info guac_client_info;
#endif

View File

@ -32,73 +32,34 @@
#include "client-fntypes.h"
#include "client-types.h"
#include "client-constants.h"
#include "instruction-types.h"
#include "layer-types.h"
#include "object-types.h"
#include "pool-types.h"
#include "socket-types.h"
#include "stream-types.h"
#include "timestamp-types.h"
#include "user-fntypes.h"
#include "user-types.h"
#include <cairo/cairo.h>
#include <pthread.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 {
/**
* The guac_socket structure to be used to communicate with the web-client.
* It is expected that the implementor of any Guacamole proxy client will
* The guac_socket structure to be used to communicate with all connected
* web-clients (users). Unlike the user-level guac_socket, this guac_socket
* will broadcast instructions to all connected users simultaneously. It
* is expected that the implementor of any Guacamole proxy client will
* provide their own mechanism of I/O for their protocol. The guac_socket
* structure is used only to communicate conveniently with the Guacamole
* web-client.
*
* Because this socket broadcasts to all connected users, this socket MUST
* NOT be used within the same thread as a "leave" or "join" handler. Doing
* so results in undefined behavior, including possible segfaults.
*/
guac_socket* socket;
@ -110,24 +71,6 @@ struct guac_client {
*/
guac_client_state state;
/**
* The time (in milliseconds) of receipt of the last sync message from
* the client.
*/
guac_timestamp last_received_timestamp;
/**
* The time (in milliseconds) that the last sync message was sent to the
* client.
*/
guac_timestamp last_sent_timestamp;
/**
* Information structure containing properties exposed by the remote
* client during the initial handshake process.
*/
guac_client_info info;
/**
* Arbitrary reference to proxy client-specific data. Implementors of a
* Guacamole proxy client can store any data they want here, which can then
@ -136,197 +79,10 @@ struct guac_client {
void* data;
/**
* Handler for server messages. If set, this function will be called
* occasionally by the Guacamole proxy to give the client a chance to
* handle messages from whichever server it is connected to.
*
* Example:
* @code
* int handle_messages(guac_client* client);
*
* int guac_client_init(guac_client* client, int argc, char** argv) {
* client->handle_messages = handle_messages;
* }
* @endcode
* The time (in milliseconds) that the last sync message was sent to the
* client.
*/
guac_client_handle_messages* handle_messages;
/**
* Handler for mouse events sent by the Gaucamole web-client.
*
* The handler takes the integer mouse X and Y coordinates, as well as
* a button mask containing the bitwise OR of all button values currently
* being pressed. Those values are:
*
* <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;
guac_timestamp last_sent_timestamp;
/**
* Handler for freeing data when the client is being unloaded.
@ -344,7 +100,7 @@ struct guac_client {
* @code
* int free_handler(guac_client* client);
*
* int guac_client_init(guac_client* client, int argc, char** argv) {
* int guac_client_init(guac_client* client) {
* client->free_handler = free_handler;
* }
* @endcode
@ -375,46 +131,6 @@ struct guac_client {
*/
guac_client_log_handler* log_handler;
/**
* Handler for get events sent by the Guacamole web-client.
*
* The handler takes a guac_object, containing the object index which will
* persist through the duration of the transfer, and the name of the stream
* being requested. It is up to the get handler to create the required body
* stream.
*
* Example:
* @code
* int get_handler(guac_client* client, guac_object* object,
* char* name);
*
* int guac_client_init(guac_client* client, int argc, char** argv) {
* client->get_handler = get_handler;
* }
* @endcode
*/
guac_client_get_handler* get_handler;
/**
* Handler for put events sent by the Guacamole web-client.
*
* The handler takes a guac_object and guac_stream, which each contain their
* respective indices which will persist through the duration of the
* transfer, the mimetype of the data being transferred, and the name of
* the stream within the object being written to.
*
* Example:
* @code
* int put_handler(guac_client* client, guac_object* object,
* guac_stream* stream, char* mimetype, char* name);
*
* int guac_client_init(guac_client* client, int argc, char** argv) {
* client->put_handler = put_handler;
* }
* @endcode
*/
guac_client_put_handler* put_handler;
/**
* Pool of buffer indices. Buffers are simply layers with negative indices.
* Note that because guac_pool always gives non-negative indices starting
@ -435,25 +151,11 @@ struct guac_client {
guac_pool* __stream_pool;
/**
* All available output streams (data going to connected client).
* All available client-level output streams (data going to all connected
* users).
*/
guac_stream* __output_streams;
/**
* All available input streams (data coming from connected client).
*/
guac_stream* __input_streams;
/**
* Pool of object indices.
*/
guac_pool* __object_pool;
/**
* All available objects (arbitrary sets of named streams).
*/
guac_object* __objects;
/**
* The unique identifier allocated for the connection, which may
* be used within the Guacamole protocol to refer to this connection.
@ -463,6 +165,105 @@ struct guac_client {
*/
char* connection_id;
/**
* Lock which is acquired when the users list is being manipulated, or when
* the users list is being iterated.
*/
pthread_rwlock_t __users_lock;
/**
* The first user within the list of all connected users, or NULL if no
* users are currently connected.
*/
guac_user* __users;
/**
* The user that first created this connection. This user will also have
* their "owner" flag set to a non-zero value. If the owner has left the
* connection, this will be NULL.
*/
guac_user* __owner;
/**
* The number of currently-connected users. This value may include inactive
* users if cleanup of those users has not yet finished.
*/
int connected_users;
/**
* Handler for join events, called whenever a new user is joining an active
* connection. Note that because users may leave the connection at any
* time, a reference to a guac_user can become invalid at any time and
* should never be maintained outside the scope of a function invoked by
* libguac to which that guac_user was passed (the scope in which the
* guac_user reference is guaranteed to be valid) UNLESS that reference is
* properly invalidated within the leave_handler.
*
* The handler is given a pointer to a newly-allocated guac_user which
* must then be initialized, if needed.
*
* Example:
* @code
* int join_handler(guac_user* user, int argc, char** argv);
*
* int guac_client_init(guac_client* client) {
* client->join_handler = join_handler;
* }
* @endcode
*/
guac_user_join_handler* join_handler;
/**
* Handler for leave events, called whenever a new user is leaving an
* active connection.
*
* The handler is given a pointer to the leaving guac_user whose custom
* data and associated resources must now be freed, if any.
*
* Example:
* @code
* int leave_handler(guac_user* user);
*
* int guac_client_init(guac_client* client) {
* client->leave_handler = leave_handler;
* }
* @endcode
*/
guac_user_leave_handler* leave_handler;
/**
* NULL-terminated array of all arguments accepted by this client , in
* order. New users will specify these arguments when they join the
* connection, and the values of those arguments will be made available to
* the function initializing newly-joined users.
*
* The guac_client_init entry point is expected to initialize this, if
* arguments are expected.
*
* Example:
* @code
* const char* __my_args[] = {
* "hostname",
* "port",
* "username",
* "password",
* NULL
* };
*
* int guac_client_init(guac_client* client) {
* client->args = __my_args;
* }
* @endcode
*/
const char** args;
/**
* Handle to the dlopen()'d plugin, which should be given to dlclose() when
* this client is freed. This is only assigned if guac_client_load_plugin()
* is used.
*/
void* __plugin_handle;
};
/**
@ -480,25 +281,6 @@ guac_client* guac_client_alloc();
*/
void guac_client_free(guac_client* client);
/**
* Call the appropriate handler defined by the given client for the given
* instruction. A comparison is made between the instruction opcode and the
* initial handler lookup table defined in client-handlers.c. The intial
* handlers will in turn call the client's handler (if defined).
*
* @param client
* The proxy client whose handlers should be called.
*
* @param instruction
* The instruction to pass to the proxy client via the appropriate handler.
*
* @return
* Non-negative if the instruction was handled successfully, or negative
* if an error occurred.
*/
int guac_client_handle_instruction(guac_client* client,
guac_instruction* instruction);
/**
* Writes a message in the log used by the given client. The logger used will
* normally be defined by guacd (or whichever program loads the proxy client)
@ -604,8 +386,11 @@ void guac_client_free_layer(guac_client* client, guac_layer* layer);
* Allocates a new stream. An arbitrary index is automatically assigned
* if no previously-allocated stream is available for use.
*
* @param client The proxy client to allocate the layer buffer for.
* @return The next available stream, or a newly allocated stream.
* @param client
* The client to allocate the stream for.
*
* @return
* The next available stream, or a newly allocated stream.
*/
guac_stream* guac_client_alloc_stream(guac_client* client);
@ -613,34 +398,134 @@ guac_stream* guac_client_alloc_stream(guac_client* client);
* Returns the given stream to the pool of available streams, such that it
* can be reused by any subsequent call to guac_client_alloc_stream().
*
* @param client The proxy client to return the buffer to.
* @param stream The stream to return to the pool of available stream.
* @param client
* The client to return the stream to.
*
* @param stream
* The stream to return to the pool of available stream.
*/
void guac_client_free_stream(guac_client* client, guac_stream* stream);
/**
* Allocates a new object. An arbitrary index is automatically assigned
* if no previously-allocated object is available for use.
* Adds the given user to the internal list of connected users. Future writes
* to the broadcast socket stored within guac_client will also write to this
* user. The join handler of this guac_client will be called.
*
* @param client
* The proxy client to allocate the object for.
*
* @return
* The next available object, or a newly allocated object.
* @param client The proxy client to add the user to.
* @param user The user to add.
* @param argc The number of arguments to pass to the new user.
* @param argv An array of strings containing the argument values being passed.
* @return Zero if the user was added successfully, non-zero if the user could
* not join the connection.
*/
guac_object* guac_client_alloc_object(guac_client* client);
int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** argv);
/**
* Returns the given object to the pool of available objects, such that it
* can be reused by any subsequent call to guac_client_alloc_object().
* Removes the given user, removing the user from the internally-tracked list
* of connected users, and calling any appropriate leave handler.
*
* @param client The proxy client to return the buffer to.
* @param user The user to remove.
*/
void guac_client_remove_user(guac_client* client, guac_user* user);
/**
* Calls the given function on all currently-connected users of the given
* client. The function will be given a reference to a guac_user and the
* specified arbitrary data. The value returned by the callback will be
* ignored.
*
* This function is reentrant, but the user list MUST NOT be manipulated
* within the same thread as a callback to this function. Though the callback
* MAY invoke guac_client_foreach_user(), doing so should not be necessary, and
* may indicate poor design choices.
*
* Because this function loops through all connected users, this function MUST
* NOT be invoked within the same thread as a "leave" or "join" handler. Doing
* so results in undefined behavior, including possible segfaults.
*
* @param client
* The proxy client to return the object to.
* The client whose users should be iterated.
*
* @param object
* The object to return to the pool of available object.
* @param callback
* The function to call for each user.
*
* @param data
* Arbitrary data to pass to the callback each time it is invoked.
*/
void guac_client_free_object(guac_client* client, guac_object* object);
void guac_client_foreach_user(guac_client* client,
guac_user_callback* callback, void* data);
/**
* Retrieves the connected user that is marked as the owner. The owner of a
* connection is the user that established the initial connection that created
* the connection (the first user to connect and join).
*
* Calls the given function on with the currently-connected user that is marked
* as the owner. The owner of a connection is the user that established the
* initial connection that created the connection (the first user to connect
* and join). The function will be given a reference to a guac_user and the
* specified arbitrary data. If the owner has since left the connection, the
* function will instead be invoked with NULL as the guac_user. The value
* returned by the callback will be returned by this function.
*
* This function is reentrant, but the user list MUST NOT be manipulated
* within the same thread as a callback to this function.
*
* Because this function depends on a consistent list of connected users, this
* function MUST NOT be invoked within the same thread as a "leave" or "join"
* handler. Doing so results in undefined behavior, including possible
* segfaults.
*
* @param client
* The client to retrieve the owner from.
*
* @return
* The value returned by the callback.
*/
void* guac_client_for_owner(guac_client* client, guac_user_callback* callback,
void* data);
/**
* Marks the end of the current frame by sending a "sync" instruction to
* all connected users. This instruction will contain the current timestamp.
* The last_sent_timestamp member of guac_client will be updated accordingly.
*
* If an error occurs sending the instruction, a non-zero value is
* returned, and guac_error is set appropriately.
*
* @param client The guac_client which has finished a frame.
* @return Zero on success, non-zero on error.
*/
int guac_client_end_frame(guac_client* client);
/**
* Initializes the given guac_client using the initialization routine provided
* by the plugin corresponding to the named protocol. This will automatically
* invoke guac_client_init within the plugin for the given protocol.
*
* Note that the connection will likely not be established until the first
* user (the "owner") is added to the client.
*
* @param client The guac_client to initialize.
* @param protocol The name of the protocol to use.
* @return Zero if initialization was successful, non-zero otherwise.
*/
int guac_client_load_plugin(guac_client* client, const char* protocol);
/**
* Calculates and returns the approximate processing lag experienced by the
* pool of users. The processing lag is the difference in time between server
* and client due purely to data processing and excluding network delays.
*
* @param client
* The guac_client to calculate the processing lag of.
*
* @return
* The approximate processing lag of the pool of users associated with the
* given guac_client, in milliseconds.
*/
int guac_client_get_processing_lag(guac_client* client);
/**
* Streams the image data of the given surface over an image stream ("img"
@ -648,7 +533,7 @@ void guac_client_free_object(guac_client* client, guac_object* object);
* allocated and freed.
*
* @param client
* The Guacamole client from which the image stream should be allocated.
* The Guacamole client for which the image stream should be allocated.
*
* @param socket
* The socket over which instructions associated with the image stream
@ -681,7 +566,7 @@ void guac_client_stream_png(guac_client* client, guac_socket* socket,
* will be automatically allocated and freed.
*
* @param client
* The Guacamole client from which the image stream should be allocated.
* The Guacamole client for which the image stream should be allocated.
*
* @param socket
* The socket over which instructions associated with the image stream
@ -705,8 +590,9 @@ void guac_client_stream_png(guac_client* client, guac_socket* socket,
* A Cairo surface containing the image data to be streamed.
*
* @param quality
* The JPEG image quality, which must be an integer value between 0 and
* 100 inclusive.
* The JPEG image quality, which must be an integer value between 0 and 100
* inclusive. Larger values indicate improving quality at the expense of
* larger file size.
*/
void guac_client_stream_jpeg(guac_client* client, guac_socket* socket,
guac_composite_mode mode, const guac_layer* layer, int x, int y,
@ -720,7 +606,7 @@ void guac_client_stream_jpeg(guac_client* client, guac_socket* socket,
* guac_client_supports_webp() prior to calling this function.
*
* @param client
* The Guacamole client from which the image stream should be allocated.
* The Guacamole client for whom the image stream should be allocated.
*
* @param socket
* The socket over which instructions associated with the image stream
@ -758,15 +644,15 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket,
cairo_surface_t* surface, int quality, int lossless);
/**
* Returns whether the given client supports WebP. If the client does not
* support WebP, or the server cannot encode WebP images, zero is returned.
* Returns whether all users of the given client support WebP. If any user does
* not support WebP, or the server cannot encode WebP images, zero is returned.
*
* @param client
* The Guacamole client to check for WebP support.
* The Guacamole client whose users should be checked for WebP support.
*
* @return
* Non-zero if the given client claims to support WebP and the server has
* been built with WebP support, zero otherwise.
* Non-zero if the all users of the given client claim to support WebP and
* the server has been built with WebP support, zero otherwise.
*/
int guac_client_supports_webp(guac_client* client);

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

View File

@ -20,13 +20,13 @@
* THE SOFTWARE.
*/
#ifndef _GUAC_INSTRUCTION_CONSTANTS_H
#define _GUAC_INSTRUCTION_CONSTANTS_H
#ifndef _GUAC_PARSER_CONSTANTS_H
#define _GUAC_PARSER_CONSTANTS_H
/**
* Constants related to Guacamole instructions.
* Constants related to the Guacamole protocol parser.
*
* @file instruction-constants.h
* @file parser-constants.h
*/
/**

View File

@ -21,48 +21,49 @@
*/
#ifndef _GUAC_INSTRUCTION_TYPES_H
#define _GUAC_INSTRUCTION_TYPES_H
#ifndef _GUAC_PARSER_TYPES_H
#define _GUAC_PARSER_TYPES_H
/**
* Type definitions related to Guacamole instructions.
* Type definitions related to parsing the Guacamole protocol.
*
* @file instruction-types.h
* @file parser-types.h
*/
/**
* All possible states of the instruction parser.
*/
typedef enum guac_instruction_parse_state {
typedef enum guac_parse_state {
/**
* The parser is currently waiting for data to complete the length prefix
* of the current element of the instruction.
*/
GUAC_INSTRUCTION_PARSE_LENGTH,
GUAC_PARSE_LENGTH,
/**
* The parser has finished reading the length prefix and is currently
* waiting for data to complete the content of the instruction.
*/
GUAC_INSTRUCTION_PARSE_CONTENT,
GUAC_PARSE_CONTENT,
/**
* The instruction has been fully parsed.
*/
GUAC_INSTRUCTION_PARSE_COMPLETE,
GUAC_PARSE_COMPLETE,
/**
* The instruction cannot be parsed because of a protocol error.
*/
GUAC_INSTRUCTION_PARSE_ERROR
GUAC_PARSE_ERROR
} guac_instruction_parse_state;
} guac_parse_state;
/**
* Represents a single instruction within the Guacamole protocol.
* A Guacamole protocol parser, which reads individual instructions, filling
* its own internal structure with the most recently read instruction data.
*/
typedef struct guac_instruction guac_instruction;
typedef struct guac_parser guac_parser;
#endif

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
#define _GUAC_PLUGIN_H
#include "client-types.h"
#include "plugin-constants.h"
#include "plugin-types.h"
/**
* Provides functions and structures required for handling a client plugin.
@ -33,60 +31,4 @@
* @file plugin.h
*/
struct guac_client_plugin {
/**
* Reference to dlopen'd client plugin.
*/
void* __client_plugin_handle;
/**
* Reference to the init handler of this client plugin. This
* function will be called when the client plugin is started.
*/
guac_client_init_handler* init_handler;
/**
* NULL-terminated array of all arguments accepted by this client
* plugin, in order. The values of these arguments will be passed
* to the init_handler if the client plugin is started.
*/
const char** args;
};
/**
* Open the plugin which provides support for the given protocol, if it
* exists.
*
* @param protocol The name of the protocol to retrieve the client plugin
* for.
* @return The client plugin supporting the given protocol, or NULL if
* an error occurs or no such plugin exists.
*/
guac_client_plugin* guac_client_plugin_open(const char* protocol);
/**
* Close the given plugin, releasing all associated resources. This function
* must be called after use of a client plugin is finished.
*
* @param plugin The client plugin to close.
* @return Zero on success, non-zero if an error occurred while releasing
* the resources associated with the plugin.
*/
int guac_client_plugin_close(guac_client_plugin* plugin);
/**
* Initializes the given guac_client using the initialization routine provided
* by the given guac_client_plugin.
*
* @param plugin The client plugin to use to initialize the new client.
* @param client The guac_client to initialize.
* @param argc The number of arguments being passed to the client.
* @param argv All arguments to be passed to the client.
* @return Zero if initialization was successful, non-zero otherwise.
*/
int guac_client_plugin_init_client(guac_client_plugin* plugin,
guac_client* client, int argc, char** argv);
#endif

View File

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

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
* POSIX write() function. When set within a guac_socket, a handler of this type
* will be called when data needs to be write into the socket.
* will be called when data needs to be written to the socket.
*
* @param socket The guac_socket being written to.
* @param buf The arbitrary buffer containing data to be written.
* @param count The maximum number of bytes to write from the buffer.
* @param count The maximum number of bytes to written to the buffer.
* @return The number of bytes written, or -1 if an error occurs.
*/
typedef ssize_t guac_socket_write_handler(guac_socket* socket,
@ -72,6 +72,39 @@ typedef ssize_t guac_socket_write_handler(guac_socket* socket,
*/
typedef int guac_socket_select_handler(guac_socket* socket, int usec_timeout);
/**
* Generic flush handler for socket flush operations. This function is not
* modeled after any POSIX function. When set within a guac_socket, a handler
* of this type will be called when guac_socket_flush() is called.
*
* @param socket
* The guac_socket being flushed.
*
* @return
* Zero on success, or non-zero if an error occurs during flush.
*/
typedef ssize_t guac_socket_flush_handler(guac_socket* socket);
/**
* When set within a guac_socket, a handler of this type will be called
* whenever exclusive access to the guac_socket is required, such as when
* guac_socket_instruction_begin() is called.
*
* @param socket
* The guac_socket to which exclusive access is required.
*/
typedef void guac_socket_lock_handler(guac_socket* socket);
/**
* When set within a guac_socket, a handler of this type will be called
* whenever exclusive access to the guac_socket is no longer required, such as
* when guac_socket_instruction_end() is called.
*
* @param socket
* The guac_socket to which exclusive access is no longer required.
*/
typedef void guac_socket_unlock_handler(guac_socket* socket);
/**
* Generic handler for the closing of a socket, modeled after the standard
* POSIX close() function. When set within a guac_socket, a handler of this type

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
* of this software and associated documentation files (the "Software"), to deal
@ -52,13 +52,28 @@ struct guac_socket {
/**
* Handler which will be called whenever data is written to this socket.
* Note that because guac_socket automatically buffers written data, this
* handler might only get called when the socket is flushed.
*/
guac_socket_write_handler* write_handler;
/**
* Handler which will be called whenever guac_socket_select is invoked
* Handler which will be called whenever this socket needs to be flushed.
*/
guac_socket_flush_handler* flush_handler;
/**
* Handler which will be called whenever a socket needs to be acquired for
* exclusive access, such as when an instruction is about to be written.
*/
guac_socket_lock_handler* lock_handler;
/**
* Handler which will be called whenever exclusive access to a socket is
* being released, such as when an instruction has finished being written.
*/
guac_socket_unlock_handler* unlock_handler;
/**
* Handler which will be called whenever guac_socket_select() is invoked
* on this socket.
*/
guac_socket_select_handler* select_handler;
@ -90,52 +105,6 @@ struct guac_socket {
*/
int __ready_buf[3];
/**
* The number of bytes currently in the main write buffer.
*/
int __written;
/**
* The main write buffer. Bytes written go here before being flushed
* to the open file descriptor.
*/
char __out_buf[GUAC_SOCKET_OUTPUT_BUFFER_SIZE];
/**
* Pointer to the first character of the current in-progress instruction
* within the buffer.
*/
char* __instructionbuf_unparsed_start;
/**
* Pointer to the first unused section of the instruction buffer.
*/
char* __instructionbuf_unparsed_end;
/**
* The instruction buffer. This is essentially the input buffer,
* provided as a convenience to be used to buffer instructions until
* those instructions are complete and ready to be parsed.
*/
char __instructionbuf[32768];
/**
* Whether instructions should be guaranteed atomic across threads using
* locks. By default, thread safety is disabled on sockets.
*/
int __threadsafe_instructions;
/**
* Lock which is acquired when an instruction is being written, and
* released when the instruction is finished being written.
*/
pthread_mutex_t __instruction_write_lock;
/**
* Lock which is acquired when the buffer is being modified or flushed.
*/
pthread_mutex_t __buffer_lock;
/**
* Whether automatic keep-alive is enabled.
*/
@ -164,66 +133,36 @@ guac_socket* guac_socket_alloc();
*/
void guac_socket_free(guac_socket* socket);
/**
* Declares that the given socket must behave in a threadsafe way. Calling
* this function on a socket guarantees that the socket will send instructions
* atomically. Without automatic threadsafe sockets, multiple threads writing
* to the same socket must ensure that instructions will not potentially
* overlap.
*
* @param socket The guac_socket to declare as threadsafe.
*/
void guac_socket_require_threadsafe(guac_socket* socket);
/**
* Declares that the given socket must automatically send a keep-alive ping
* to ensure neither side of the socket times out while the socket is open.
* This ping will take the form of a "nop" instruction. Enabling keep-alive
* automatically enables threadsafety.
* This ping will take the form of a "nop" instruction.
*
* @param socket The guac_socket to declare as requiring an automatic
* keep-alive ping.
* @param socket
* The guac_socket to declare as requiring an automatic keep-alive ping.
*/
void guac_socket_require_keep_alive(guac_socket* socket);
/**
* Marks the beginning of a Guacamole protocol instruction. If threadsafety
* is enabled on the socket, other instructions will be blocked from sending
* until this instruction is complete.
* Marks the beginning of a Guacamole protocol instruction.
*
* @param socket The guac_socket beginning an instruction.
* @param socket
* The guac_socket beginning an instruction.
*/
void guac_socket_instruction_begin(guac_socket* socket);
/**
* Marks the end of a Guacamole protocol instruction. If threadsafety
* is enabled on the socket, other instructions will be allowed to send.
* Marks the end of a Guacamole protocol instruction.
*
* @param socket The guac_socket ending an instruction.
* @param socket
* The guac_socket ending an instruction.
*/
void guac_socket_instruction_end(guac_socket* socket);
/**
* Marks the beginning of a socket's buffer modification. If threadsafety is
* enabled on the socket, other functions which modify the buffer will be
* blocked until this modification is complete.
*
* @param socket The guac_socket whose buffer is being updated.
*/
void guac_socket_update_buffer_begin(guac_socket* socket);
/**
* Marks the end of a socket's buffer modification. If threadsafety is enabled
* on the socket, other functions which modify the buffer will now be allowed
* to continue.
*
* @param socket The guac_socket whose buffer is done being updated.
*/
void guac_socket_update_buffer_end(guac_socket* socket);
/**
* Allocates and initializes a new guac_socket object with the given open
* file descriptor.
* file descriptor. The file descriptor will be automatically closed when
* the allocated guac_socket is freed.
*
* If an error occurs while allocating the guac_socket object, NULL is returned,
* and guac_error is set appropriately.
@ -237,7 +176,8 @@ guac_socket* guac_socket_open(int fd);
/**
* Allocates and initializes a new guac_socket which writes all data via
* nest instructions to the given existing, open guac_socket.
* nest instructions to the given existing, open guac_socket. Freeing the
* returned guac_socket has no effect on the underlying, nested guac_socket.
*
* If an error occurs while allocating the guac_socket object, NULL is returned,
* and guac_error is set appropriately.
@ -268,9 +208,7 @@ ssize_t guac_socket_write_int(guac_socket* socket, int64_t i);
/**
* Writes the given string to the given guac_socket object. The data
* written may be buffered until the buffer is flushed automatically or
* manually. Note that if the string can contain characters used
* internally by the Guacamole protocol (commas, semicolons, or
* backslashes) it will need to be escaped.
* manually.
*
* If an error occurs while writing, a non-zero value is returned, and
* guac_error is set appropriately.
@ -284,9 +222,9 @@ ssize_t guac_socket_write_string(guac_socket* socket, const char* str);
/**
* Writes the given binary data to the given guac_socket object as base64-
* encoded data. The data written may be buffered until the buffer is flushed
* automatically or manually. Beware that because base64 data is buffered
* automatically or manually. Beware that, because base64 data is buffered
* on top of the write buffer already used, a call to guac_socket_flush_base64()
* must be made before non-base64 writes (or writes of an independent block of
* MUST be made before non-base64 writes (or writes of an independent block of
* base64 data) can be made.
*
* If an error occurs while writing, a non-zero value is returned, and
@ -300,8 +238,8 @@ ssize_t guac_socket_write_string(guac_socket* socket, const char* str);
ssize_t guac_socket_write_base64(guac_socket* socket, const void* buf, size_t count);
/**
* Writes the given data to the specified socket. The data written is not
* buffered, and will be sent immediately.
* Writes the given data to the specified socket. The data written may be
* buffered until the buffer is flushed automatically or manually.
*
* If an error occurs while writing, a non-zero value is returned, and
* guac_error is set appropriately.

View File

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

View File

@ -37,9 +37,18 @@
* calls. The return value from a single call will not have any useful
* (or defined) meaning.
*
* @return An arbitrary millisecond timestamp.
* @return
* An arbitrary millisecond timestamp.
*/
guac_timestamp guac_timestamp_current();
/**
* Sleeps for the given number of milliseconds.
*
* @param duration
* The number of milliseconds to sleep.
*/
void guac_timestamp_msleep(int duration);
#endif

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,408 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef _GUAC_USER_FNTYPES_H
#define _GUAC_USER_FNTYPES_H
/**
* Function type definitions related to the guac_user object.
*
* @file user-fntypes.h
*/
#include "object-types.h"
#include "protocol-types.h"
#include "stream-types.h"
#include "timestamp-types.h"
#include "user-types.h"
/**
* Callback which relates to a single guac_user at a time, along with arbitrary
* data.
*
* @see guac_client_foreach_user()
* @see guac_client_for_owner()
*
* @param user
* The user for which this callback was invoked. Depending on whether
* guac_client_foreach_user() or guac_client_for_owner() was called, this
* will either be the current user as the "foreach" iteration continues,
* or the owner of the connection. If guac_client_for_owner() was called
* for a connection which has no owner, this may be NULL.
*
* @param data
* The arbitrary data passed to guac_client_foreach_user() or
* guac_client_for_owner().
*
* @return
* An arbitrary return value, the semantics of which are determined by the
* implementation of the callback and the manner of its user. In the case
* of a callback provided to guac_client_foreach_user(), this value is
* always discarded.
*/
typedef void* guac_user_callback(guac_user* user, void* data);
/**
* Handler for Guacamole mouse events, invoked when a "mouse" instruction has
* been received from a user.
*
* @param user
* The user that sent the mouse event.
*
* @param x
* The X coordinate of the mouse within the display when the event
* occurred, in pixels. This value is not guaranteed to be within the
* bounds of the display area.
*
* @param y
* The Y coordinate of the mouse within the display when the event
* occurred, in pixels. This value is not guaranteed to be within the
* bounds of the display area.
*
* @param button_mask
* An integer value representing the current state of each button, where
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
* button is currently pressed. The lowest-order bit is the left mouse
* button, followed by the middle button, right button, and finally the up
* and down buttons of the scroll wheel.
*
* @see GUAC_CLIENT_MOUSE_LEFT
* @see GUAC_CLIENT_MOUSE_MIDDLE
* @see GUAC_CLIENT_MOUSE_RIGHT
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
*
* @return
* Zero if the mouse event was handled successfully, or non-zero if an
* error occurred.
*/
typedef int guac_user_mouse_handler(guac_user* user, int x, int y,
int button_mask);
/**
* Handler for Guacamole key events, invoked when a "key" event has been
* received from a user.
*
* @param user
* The user that sent the key event.
*
* @param keysym
* The X11 keysym of the key that was pressed or released.
*
* @param pressed
* Non-zero if the key represented by the given keysym is currently
* pressed, zero if it is released.
*
* @return
* Zero if the key event was handled successfully, or non-zero if an error
* occurred.
*/
typedef int guac_user_key_handler(guac_user* user, int keysym, int pressed);
/**
* Handler for Guacamole clipboard streams received from a user. Each such
* clipboard stream begins when the user sends a "clipboard" instruction. To
* handle received data along this stream, implementations of this handler
* must assign blob and end handlers to the given stream object.
*
* @param user
* The user that opened the clipboard stream.
*
* @param stream
* The stream object allocated by libguac to represent the clipboard stream
* opened by the user.
*
* @param mimetype
* The mimetype of the data that will be sent along the stream.
*
* @return
* Zero if the opening of the clipboard stream has been handled
* successfully, or non-zero if an error occurs.
*/
typedef int guac_user_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype);
/**
* Handler for Guacamole size events, invoked when a "size" instruction has
* been received from a user. A "size" instruction indicates that the desired
* display size has changed.
*
* @param user
* The user whose desired display size has changed.
*
* @param width
* The desired width of the display, in pixels.
*
* @param height
* The desired height of the display, in pixels.
*
* @return
* Zero if the size event has been successfully handled, non-zero
* otherwise.
*/
typedef int guac_user_size_handler(guac_user* user,
int width, int height);
/**
* Handler for Guacamole file streams received from a user. Each such file
* stream begins when the user sends a "file" instruction. To handle received
* data along this stream, implementations of this handler must assign blob and
* end handlers to the given stream object.
*
* @param user
* The user that opened the file stream.
*
* @param stream
* The stream object allocated by libguac to represent the file stream
* opened by the user.
*
* @param mimetype
* The mimetype of the data that will be sent along the stream.
*
* @param filename
* The name of the file being transferred.
*
* @return
* Zero if the opening of the file stream has been handled successfully, or
* non-zero if an error occurs.
*/
typedef int guac_user_file_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* filename);
/**
* Handler for Guacamole pipe streams received from a user. Pipe streams are
* unidirectional, arbitrary, named pipes. Each such pipe stream begins when
* the user sends a "pipe" instruction. To handle received data along this
* stream, implementations of this handler must assign blob and end handlers to
* the given stream object.
*
* @param user
* The user that opened the pipe stream.
*
* @param stream
* The stream object allocated by libguac to represent the pipe stream
* opened by the user.
*
* @param mimetype
* The mimetype of the data that will be sent along the stream.
*
* @param name
* The arbitrary name assigned to this pipe. It is up to the implementation
* of this handler and the application containing the Guacamole client to
* determine the semantics of a pipe stream having this name.
*
* @return
* Zero if the opening of the pipe stream has been handled successfully, or
* non-zero if an error occurs.
*/
typedef int guac_user_pipe_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name);
/**
* Handler for Guacamole stream blobs. Each blob originates from a "blob"
* instruction which was associated with a previously-created stream.
*
* @param user
* The user that is sending this blob of data along the stream.
*
* @param stream
* The stream along which the blob was received. The semantics associated
* with this stream are determined by the manner of its creation.
*
* @param data
* The blob of data received.
*
* @param length
* The number of bytes within the blob of data received.
*
* @return
* Zero if the blob of data was successfully handled, non-zero otherwise.
*/
typedef int guac_user_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length);
/**
* Handler for Guacamole stream "ack" instructions. A user will send "ack"
* instructions to acknowledge the successful receipt of blobs along a stream
* opened by the server, or to notify of errors. An "ack" with an error status
* implicitly closes the stream.
*
* @param user
* The user sending the "ack" instruction.
*
* @param stream
* The stream for which the "ack" was received.
*
* @param error
* An arbitrary, human-readable message describing the error that
* occurred, if any. If no error occurs, this will likely be blank,
* "SUCCESS", or similar. This value exists for the sake of readability,
* not for the sake of data interchange.
*
* @param status
* GUAC_PROTOCOL_STATUS_SUCCESS if the blob was received and handled
* successfully, or a different status code describing the problem if an
* error occurred and the stream has been implicitly closed.
*
* @return
* Zero if the "ack" message was successfully handled, non-zero otherwise.
*/
typedef int guac_user_ack_handler(guac_user* user, guac_stream* stream,
char* error, guac_protocol_status status);
/**
* Handler for Guacamole stream "end" instructions. End instructions are sent
* by the user when a stream is closing because its end has been reached.
*
* @param user
* The user that sent the "end" instruction.
*
* @param stream
* The stream that is being closed.
*
* @return
* Zero if the end-of-stream condition has been sucessfully handled,
* non-zero otherwise.
*/
typedef int guac_user_end_handler(guac_user* user, guac_stream* stream);
/**
* Handler for Guacamole join events. A join event is fired by the
* guac_client whenever a guac_user joins the connection. There is no
* instruction associated with a join event.
*
* Implementations of the join handler MUST NOT use the client-level
* broadcast socket, nor invoke guac_client_foreach_user() or
* guac_client_for_owner(). Doing so will result in undefined behavior,
* including segfaults.
*
* @param user
* The user joining the connection. The guac_client associated with the
* connection will already be populated within the user object.
*
* @param argc
* The number of arguments stored within argv.
*
* @param argv
* An array of all arguments provided by the user when they joined. These
* arguments must correspond to the argument names declared when the
* guac_client was initialized. If the number of arguments does not match
* the number of argument names declared, then the joining user has
* violated the Guacamole protocol.
*
* @return
* Zero if the user has been successfully initialized and should be allowed
* to join the connection, non-zero otherwise.
*/
typedef int guac_user_join_handler(guac_user* user, int argc, char** argv);
/**
* Handler for Guacamole leave events. A leave event is fired by the
* guac_client whenever a guac_user leaves the connection. There is no
* instruction associated with a leave event.
*
* Implementations of the leave handler MUST NOT use the client-level
* broadcast socket, nor invoke guac_client_foreach_user() or
* guac_client_for_owner(). Doing so will result in undefined behavior,
* including segfaults.
*
* @param user
* The user that has left the connection.
*
* @return
* Zero if the leave event has been successfully handled, non-zero
* otherwise.
*/
typedef int guac_user_leave_handler(guac_user* user);
/**
* Handler for Guacamole sync events. A sync event is fired by the
* guac_client whenever a guac_user responds to a "sync" instruction. Sync
* instructions are sent by the Guacamole server to mark the logical end of a
* frame, and to inform the Guacamole client that all data up to a particular
* point in time has been sent. The response from the Guacamole client
* similarly indicates that all data received up to a particular point in
* server time has been handled.
*
* @param user
* The user that sent the "sync" instruction.
*
* @param timestamp
* The timestamp contained within the sync instruction.
*
* @return
* Zero if the sync event has been handled successfully, non-zero
* otherwise.
*/
typedef int guac_user_sync_handler(guac_user* user, guac_timestamp timestamp);
/**
* Handler for Guacamole object get requests. The semantics of the stream
* which will be created in response to the request are determined by the type
* of the object and the name of the stream requested. It is up to the
* implementation of this handler to then respond with a "body" instruction
* that begins the requested stream.
*
* @param user
* The user requesting read access to the stream having the given name.
*
* @param object
* The object from which the given named stream is being requested.
*
* @param name
* The name of the stream being requested.
*
* @return
* Zero if the get request was successfully handled, non-zero otherwise.
*/
typedef int guac_user_get_handler(guac_user* user, guac_object* object,
char* name);
/**
* Handler for Guacamole object put requests. Put requests implicitly create a
* stream, the semantics of which are determined by the type of the object
* and the name of the stream requested.
*
* @param user
* The user requesting write access to the stream having the given name.
*
* @param object
* The object from which the given named stream is being requested.
*
* @param stream
* The stream along which the blobs which should be written to the named
* stream will be received.
*
* @param mimetype
* The mimetype of the data that will be received along the given stream.
*
* @param name
* The name of the stream being requested.
*
* @return
* Zero if the put request was successfully handled, non-zero otherwise.
*/
typedef int guac_user_put_handler(guac_user* user, guac_object* object,
guac_stream* stream, char* mimetype, char* name);
#endif

View File

@ -20,21 +20,26 @@
* THE SOFTWARE.
*/
#ifndef _GUAC_PLUGIN_TYPES_H
#define _GUAC_PLUGIN_TYPES_H
#ifndef _GUAC_USER_TYPES_H
#define _GUAC_USER_TYPES_H
/**
* Type definitions related to client plugins.
* Type definitions related to the guac_user object.
*
* @file plugin-types.h
* @file user-types.h
*/
/**
* A handle to a client plugin, containing enough information about the
* plugin to complete the initial protocol handshake and instantiate a new
* client supporting the protocol provided by the client plugin.
* Representation of a physical connection within a larger logical connection
* which may be shared. Logical connections are represented by guac_client.
*/
typedef struct guac_client_plugin guac_client_plugin;
typedef struct guac_user guac_user;
/**
* Information exposed by the remote client during the connection handshake
* which can be used by a client plugin.
*/
typedef struct guac_user_info guac_user_info;
#endif

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

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

View File

@ -29,14 +29,15 @@
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
static void raw_encoder_begin_handler(guac_audio_stream* audio) {
static void raw_encoder_send_audio(guac_audio_stream* audio,
guac_socket* socket) {
raw_encoder_state* state;
char mimetype[256];
/* Produce mimetype string from format info */
@ -44,7 +45,16 @@ static void raw_encoder_begin_handler(guac_audio_stream* audio) {
audio->bps, audio->rate, audio->channels);
/* Associate stream */
guac_protocol_send_audio(audio->client->socket, audio->stream, mimetype);
guac_protocol_send_audio(socket, audio->stream, mimetype);
}
static void raw_encoder_begin_handler(guac_audio_stream* audio) {
raw_encoder_state* state;
/* Broadcast existence of stream */
raw_encoder_send_audio(audio, audio->client->socket);
/* Allocate and init encoder state */
audio->data = state = malloc(sizeof(raw_encoder_state));
@ -55,9 +65,13 @@ static void raw_encoder_begin_handler(guac_audio_stream* audio) {
state->buffer = malloc(state->length);
guac_client_log(audio->client, GUAC_LOG_DEBUG,
"Using raw encoder (%s) with a %i byte buffer.",
mimetype, state->length);
}
static void raw_encoder_join_handler(guac_audio_stream* audio,
guac_user* user) {
/* Notify user of existence of stream */
raw_encoder_send_audio(audio, user->socket);
}
@ -143,6 +157,7 @@ guac_audio_encoder _raw8_encoder = {
.begin_handler = raw_encoder_begin_handler,
.write_handler = raw_encoder_write_handler,
.flush_handler = raw_encoder_flush_handler,
.join_handler = raw_encoder_join_handler,
.end_handler = raw_encoder_end_handler
};
@ -152,6 +167,7 @@ guac_audio_encoder _raw16_encoder = {
.begin_handler = raw_encoder_begin_handler,
.write_handler = raw_encoder_write_handler,
.flush_handler = raw_encoder_flush_handler,
.join_handler = raw_encoder_join_handler,
.end_handler = raw_encoder_end_handler
};

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
* of this software and associated documentation files (the "Software"), to deal
@ -25,9 +25,11 @@
#include "error.h"
#include "socket.h"
#include <pthread.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
@ -37,34 +39,69 @@
#include <sys/select.h>
#endif
typedef struct __guac_socket_fd_data {
/**
* Data associated with an open socket which writes to a file descriptor.
*/
typedef struct guac_socket_fd_data {
/**
* The associated file descriptor;
*/
int fd;
} __guac_socket_fd_data;
/**
* The number of bytes currently in the main write buffer.
*/
int written;
ssize_t __guac_socket_fd_read_handler(guac_socket* socket,
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 */
if (retval < 0) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Error reading data from socket";
}
} guac_socket_fd_data;
return retval;
}
ssize_t __guac_socket_fd_write_handler(guac_socket* socket,
/**
* Writes the entire contents of the given buffer to the file descriptor
* associated with the given socket, retrying as necessary until the whole
* buffer is written, and aborting if an error occurs.
*
* @param socket
* The guac_socket associated with the file descriptor to which the given
* buffer should be written.
*
* @param buf
* The buffer of data to write to the given guac_socket.
*
* @param count
* The number of bytes within the given buffer.
*
* @return
* The number of bytes written, which will be exactly the size of the given
* buffer, or a negative value if an error occurs.
*/
ssize_t guac_socket_fd_write(guac_socket* socket,
const void* buf, size_t count) {
__guac_socket_fd_data* data = (__guac_socket_fd_data*) socket->data;
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
const char* buffer = buf;
/* Write until completely written */
while (count > 0) {
int retval;
#ifdef __MINGW32__
@ -79,14 +116,229 @@ ssize_t __guac_socket_fd_write_handler(guac_socket* socket,
if (retval < 0) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Error writing data to socket";
return retval;
}
/* Advance buffer as data retval */
buffer += retval;
count -= retval;
}
return 0;
}
/**
* Attempts to read from the underlying file descriptor of the given
* guac_socket, populating the given buffer.
*
* @param socket
* The guac_socket being read from.
*
* @param buf
* The arbitrary buffer which we must populate with data.
*
* @param count
* The maximum number of bytes to read into the buffer.
*
* @return
* The number of bytes read, or -1 if an error occurs.
*/
static ssize_t guac_socket_fd_read_handler(guac_socket* socket,
void* buf, size_t count) {
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
/* 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;
}
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;
}
/**
* Flushes the internal buffer of the given guac_socket, writing all data
* to the underlying file descriptor.
*
* @param socket
* The guac_socket to flush.
*
* @return
* Zero if the flush operation was successful, non-zero otherwise.
*/
static ssize_t guac_socket_fd_flush_handler(guac_socket* socket) {
int retval;
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
/* Acquire exclusive access to buffer */
pthread_mutex_lock(&(data->buffer_lock));
/* Flush contents of buffer */
retval = guac_socket_fd_flush(socket);
/* Relinquish exclusive access to buffer */
pthread_mutex_unlock(&(data->buffer_lock));
return retval;
}
/**
* Writes the contents of the buffer to the output buffer of the given socket,
* flushing the output buffer as necessary, without first locking access to the
* output buffer. This function must ONLY be called if the buffer lock has
* already been acquired.
*
* @param socket
* The guac_socket to write the given buffer to.
*
* @param buf
* The buffer to write to the given socket.
*
* @param count
* The number of bytes in the given buffer.
*
* @return
* The number of bytes written, or a negative value if an error occurs
* during write.
*/
static ssize_t guac_socket_fd_write_buffered(guac_socket* socket,
const void* buf, size_t count) {
size_t original_count = count;
const char* current = buf;
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
/* Append to buffer, flush if necessary */
while (count > 0) {
int chunk_size;
int remaining = sizeof(data->out_buf) - data->written;
/* If no space left in buffer, flush and retry */
if (remaining == 0) {
/* Abort if error occurs during flush */
if (guac_socket_fd_flush(socket))
return -1;
/* Retry buffer append */
continue;
}
/* Calculate size of chunk to be written to buffer */
chunk_size = count;
if (chunk_size > remaining)
chunk_size = remaining;
/* Update output buffer */
memcpy(data->out_buf + data->written, current, chunk_size);
data->written += chunk_size;
/* Update provided buffer */
current += chunk_size;
count -= chunk_size;
}
/* All bytes have been written, possibly some to the internal buffer */
return original_count;
}
/**
* Appends the provided data to the internal buffer for future writing. The
* actual write attempt will occur only upon flush, or when the internal buffer
* is full.
*
* @param socket
* The guac_socket being write to.
*
* @param buf
* The arbitrary buffer containing the data to be written.
*
* @param count
* The number of bytes contained within the buffer.
*
* @return
* The number of bytes written, or -1 if an error occurs.
*/
static ssize_t guac_socket_fd_write_handler(guac_socket* socket,
const void* buf, size_t count) {
int retval;
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
/* Acquire exclusive access to buffer */
pthread_mutex_lock(&(data->buffer_lock));
/* Write provided data to buffer */
retval = guac_socket_fd_write_buffered(socket, buf, count);
/* Relinquish exclusive access to buffer */
pthread_mutex_unlock(&(data->buffer_lock));
return retval;
}
/**
* Waits for data on the underlying file desriptor of the given socket to
* become available such that the next read operation will not block.
*
* @param socket
* The guac_socket to wait for.
*
* @param usec_timeout
* The maximum amount of time to wait for data, in microseconds, or -1 to
* potentially wait forever.
*
* @return
* A positive value on success, zero if the timeout elapsed and no data is
* available, or a negative value if an error occurs.
*/
static int guac_socket_fd_select_handler(guac_socket* socket,
int usec_timeout) {
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
fd_set fds;
struct timeval timeout;
@ -122,20 +374,91 @@ int __guac_socket_fd_select_handler(guac_socket* socket, int usec_timeout) {
}
/**
* Frees all implementation-specific data associated with the given socket, but
* not the socket object itself.
*
* @param socket
* The guac_socket whose associated data should be freed.
*
* @return
* Zero if the data was successfully freed, non-zero otherwise. This
* implementation always succeeds, and will always return zero.
*/
static int guac_socket_fd_free_handler(guac_socket* socket) {
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
/* Destroy locks */
pthread_mutex_destroy(&(data->socket_lock));
pthread_mutex_destroy(&(data->buffer_lock));
/* Close file descriptor */
close(data->fd);
free(data);
return 0;
}
/**
* Acquires exclusive access to the given socket.
*
* @param socket
* The guac_socket to which exclusive access is required.
*/
static void guac_socket_fd_lock_handler(guac_socket* socket) {
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
/* Acquire exclusive access to socket */
pthread_mutex_lock(&(data->socket_lock));
}
/**
* Relinquishes exclusive access to the given socket.
*
* @param socket
* The guac_socket to which exclusive access is no longer required.
*/
static void guac_socket_fd_unlock_handler(guac_socket* socket) {
guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data;
/* Relinquish exclusive access to socket */
pthread_mutex_unlock(&(data->socket_lock));
}
guac_socket* guac_socket_open(int fd) {
pthread_mutexattr_t lock_attributes;
/* Allocate socket and associated data */
guac_socket* socket = guac_socket_alloc();
__guac_socket_fd_data* data = malloc(sizeof(__guac_socket_fd_data));
guac_socket_fd_data* data = malloc(sizeof(guac_socket_fd_data));
/* Store file descriptor as socket data */
data->fd = fd;
data->written = 0;
socket->data = data;
pthread_mutexattr_init(&lock_attributes);
pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED);
/* Init locks */
pthread_mutex_init(&(data->socket_lock), &lock_attributes);
pthread_mutex_init(&(data->buffer_lock), &lock_attributes);
/* Set read/write handlers */
socket->read_handler = __guac_socket_fd_read_handler;
socket->write_handler = __guac_socket_fd_write_handler;
socket->select_handler = __guac_socket_fd_select_handler;
socket->read_handler = guac_socket_fd_read_handler;
socket->write_handler = guac_socket_fd_write_handler;
socket->select_handler = guac_socket_fd_select_handler;
socket->lock_handler = guac_socket_fd_lock_handler;
socket->unlock_handler = guac_socket_fd_unlock_handler;
socket->flush_handler = guac_socket_fd_flush_handler;
socket->free_handler = guac_socket_fd_free_handler;
return socket;

View File

@ -105,6 +105,27 @@ ssize_t __guac_socket_nest_write_handler(guac_socket* socket,
}
/**
* Frees all implementation-specific data associated with the given socket, but
* not the socket object itself.
*
* @param socket
* The guac_socket whose associated data should be freed.
*
* @return
* Zero if the data was successfully freed, non-zero otherwise. This
* implementation always succeeds, and will always return zero.
*/
static int __guac_socket_nest_free_handler(guac_socket* socket) {
/* Free associated data */
__guac_socket_nest_data* data = (__guac_socket_nest_data*) socket->data;
free(data);
return 0;
}
guac_socket* guac_socket_nest(guac_socket* parent, int index) {
/* Allocate socket and associated data */
@ -115,8 +136,9 @@ guac_socket* guac_socket_nest(guac_socket* parent, int index) {
data->parent = parent;
socket->data = data;
/* Set write handler */
/* Set write and free handlers */
socket->write_handler = __guac_socket_nest_write_handler;
socket->free_handler = __guac_socket_nest_free_handler;
return socket;

View File

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

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

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

@ -0,0 +1,525 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "client.h"
#include "object.h"
#include "protocol.h"
#include "stream.h"
#include "timestamp.h"
#include "user.h"
#include "user-handlers.h"
#include <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}
};
/**
* Parses a 64-bit integer from the given string. It is assumed that the string
* will contain only decimal digits, with an optional leading minus sign.
* The result of parsing a string which does not conform to this pattern is
* undefined.
*
* @param str
* The string to parse, which must contain only decimal digits and an
* optional leading minus sign.
*
* @return
* The 64-bit integer value represented by the given string.
*/
static int64_t __guac_parse_int(const char* str) {
int sign = 1;
int64_t num = 0;
for (; *str != '\0'; str++) {
if (*str == '-')
sign = -sign;
else
num = num * 10 + (*str - '0');
}
return num * sign;
}
/* Guacamole instruction handlers */
int __guac_handle_sync(guac_user* user, int argc, char** argv) {
int frame_duration;
guac_timestamp current = guac_timestamp_current();
guac_timestamp timestamp = __guac_parse_int(argv[0]);
/* Error if timestamp is in future */
if (timestamp > user->client->last_sent_timestamp)
return -1;
/* Update stored timestamp */
user->last_received_timestamp = timestamp;
/* Calculate length of frame, including network and processing lag */
frame_duration = current - timestamp;
/* Update lag statistics if at least one frame has been rendered */
if (user->last_frame_duration != 0) {
/* Approximate processing lag by summing the frame duration deltas */
int processing_lag = user->processing_lag + frame_duration
- user->last_frame_duration;
/* Adjust back to zero if cumulative error leads to a negative value */
if (processing_lag < 0)
processing_lag = 0;
user->processing_lag = processing_lag;
}
/* Record duration of frame */
user->last_frame_duration = frame_duration;
if (user->sync_handler)
return user->sync_handler(user, timestamp);
return 0;
}
int __guac_handle_mouse(guac_user* user, int argc, char** argv) {
if (user->mouse_handler)
return user->mouse_handler(
user,
atoi(argv[0]), /* x */
atoi(argv[1]), /* y */
atoi(argv[2]) /* mask */
);
return 0;
}
int __guac_handle_key(guac_user* user, int argc, char** argv) {
if (user->key_handler)
return user->key_handler(
user,
atoi(argv[0]), /* keysym */
atoi(argv[1]) /* pressed */
);
return 0;
}
/**
* Retrieves the existing user-level input stream having the given index. These
* will be streams which were created by the remotely-connected user. If the
* index is invalid or too large, this function will automatically respond with
* an "ack" instruction containing an appropriate error code.
*
* @param user
* The user associated with the stream being retrieved.
*
* @param stream_index
* The index of the stream to retrieve.
*
* @return
* The stream associated with the given user and having the given index,
* or NULL if the index is invalid.
*/
static guac_stream* __get_input_stream(guac_user* user, int stream_index) {
/* Validate stream index */
if (stream_index < 0 || stream_index >= GUAC_USER_MAX_STREAMS) {
guac_stream dummy_stream;
dummy_stream.index = stream_index;
guac_protocol_send_ack(user->socket, &dummy_stream,
"Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
return NULL;
}
return &(user->__input_streams[stream_index]);
}
/**
* Retrieves the existing, in-progress (open) user-level input stream having
* the given index. These will be streams which were created by the
* remotely-connected user. If the index is invalid, too large, or the stream
* is closed, this function will automatically respond with an "ack"
* instruction containing an appropriate error code.
*
* @param user
* The user associated with the stream being retrieved.
*
* @param stream_index
* The index of the stream to retrieve.
*
* @return
* The in-progress (open)stream associated with the given user and having
* the given index, or NULL if the index is invalid or the stream is
* closed.
*/
static guac_stream* __get_open_input_stream(guac_user* user, int stream_index) {
guac_stream* stream = __get_input_stream(user, stream_index);
/* Fail if no such stream */
if (stream == NULL)
return NULL;
/* Validate initialization of stream */
if (stream->index == GUAC_USER_CLOSED_STREAM_INDEX) {
guac_stream dummy_stream;
dummy_stream.index = stream_index;
guac_protocol_send_ack(user->socket, &dummy_stream,
"Invalid stream index", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
return NULL;
}
return stream;
}
/**
* Initializes and returns a new user-level input stream having the given
* index, clearing any values that may have been assigned by a past use of the
* underlying stream object storage. If the stream was already open, it will
* first be closed and its end handlers invoked as if explicitly closed by the
* user.
*
* @param user
* The user associated with the stream being initialized.
*
* @param stream_index
* The index of the stream to initialized.
*
* @return
* A new initialized user-level input stream having the given index, or
* NULL if the index is invalid.
*/
static guac_stream* __init_input_stream(guac_user* user, int stream_index) {
guac_stream* stream = __get_input_stream(user, stream_index);
/* Fail if no such stream */
if (stream == NULL)
return NULL;
/* Force end of previous stream if open */
if (stream->index != GUAC_USER_CLOSED_STREAM_INDEX) {
/* Call stream handler if defined */
if (stream->end_handler)
stream->end_handler(user, stream);
/* Fall back to global handler if defined */
else if (user->end_handler)
user->end_handler(user, stream);
}
/* Initialize stream */
stream->index = stream_index;
stream->data = NULL;
stream->ack_handler = NULL;
stream->blob_handler = NULL;
stream->end_handler = NULL;
return stream;
}
int __guac_handle_clipboard(guac_user* user, int argc, char** argv) {
/* Pull corresponding stream */
int stream_index = atoi(argv[0]);
guac_stream* stream = __init_input_stream(user, stream_index);
if (stream == NULL)
return 0;
/* If supported, call handler */
if (user->clipboard_handler)
return user->clipboard_handler(
user,
stream,
argv[1] /* mimetype */
);
/* Otherwise, abort */
guac_protocol_send_ack(user->socket, stream,
"Clipboard unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
return 0;
}
int __guac_handle_size(guac_user* user, int argc, char** argv) {
if (user->size_handler)
return user->size_handler(
user,
atoi(argv[0]), /* width */
atoi(argv[1]) /* height */
);
return 0;
}
int __guac_handle_file(guac_user* user, int argc, char** argv) {
/* Pull corresponding stream */
int stream_index = atoi(argv[0]);
guac_stream* stream = __init_input_stream(user, stream_index);
if (stream == NULL)
return 0;
/* If supported, call handler */
if (user->file_handler)
return user->file_handler(
user,
stream,
argv[1], /* mimetype */
argv[2] /* filename */
);
/* Otherwise, abort */
guac_protocol_send_ack(user->socket, stream,
"File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
return 0;
}
int __guac_handle_pipe(guac_user* user, int argc, char** argv) {
/* Pull corresponding stream */
int stream_index = atoi(argv[0]);
guac_stream* stream = __init_input_stream(user, stream_index);
if (stream == NULL)
return 0;
/* If supported, call handler */
if (user->pipe_handler)
return user->pipe_handler(
user,
stream,
argv[1], /* mimetype */
argv[2] /* name */
);
/* Otherwise, abort */
guac_protocol_send_ack(user->socket, stream,
"Named pipes unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
return 0;
}
int __guac_handle_ack(guac_user* user, int argc, char** argv) {
guac_stream* stream;
/* Parse stream index */
int stream_index = atoi(argv[0]);
/* Ignore indices of client-level streams */
if (stream_index % 2 != 0)
return 0;
/* Determine index within user-level array of streams */
stream_index /= 2;
/* Validate stream index */
if (stream_index < 0 || stream_index >= GUAC_USER_MAX_STREAMS)
return 0;
stream = &(user->__output_streams[stream_index]);
/* Validate initialization of stream */
if (stream->index == GUAC_USER_CLOSED_STREAM_INDEX)
return 0;
/* Call stream handler if defined */
if (stream->ack_handler)
return stream->ack_handler(user, stream, argv[1],
atoi(argv[2]));
/* Fall back to global handler if defined */
if (user->ack_handler)
return user->ack_handler(user, stream, argv[1],
atoi(argv[2]));
return 0;
}
int __guac_handle_blob(guac_user* user, int argc, char** argv) {
int stream_index = atoi(argv[0]);
guac_stream* stream = __get_open_input_stream(user, stream_index);
/* Fail if no such stream */
if (stream == NULL)
return 0;
/* Call stream handler if defined */
if (stream->blob_handler) {
int length = guac_protocol_decode_base64(argv[1]);
return stream->blob_handler(user, stream, argv[1],
length);
}
/* Fall back to global handler if defined */
if (user->blob_handler) {
int length = guac_protocol_decode_base64(argv[1]);
return user->blob_handler(user, stream, argv[1],
length);
}
guac_protocol_send_ack(user->socket, stream,
"File transfer unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
return 0;
}
int __guac_handle_end(guac_user* user, int argc, char** argv) {
int result = 0;
int stream_index = atoi(argv[0]);
guac_stream* stream = __get_open_input_stream(user, stream_index);
/* Fail if no such stream */
if (stream == NULL)
return 0;
/* Call stream handler if defined */
if (stream->end_handler)
result = stream->end_handler(user, stream);
/* Fall back to global handler if defined */
else if (user->end_handler)
result = user->end_handler(user, stream);
/* Mark stream as closed */
stream->index = GUAC_USER_CLOSED_STREAM_INDEX;
return result;
}
int __guac_handle_get(guac_user* user, int argc, char** argv) {
guac_object* object;
/* Validate object index */
int object_index = atoi(argv[0]);
if (object_index < 0 || object_index >= GUAC_USER_MAX_OBJECTS)
return 0;
object = &(user->__objects[object_index]);
/* Validate initialization of object */
if (object->index == GUAC_USER_UNDEFINED_OBJECT_INDEX)
return 0;
/* Call object handler if defined */
if (object->get_handler)
return object->get_handler(
user,
object,
argv[1] /* name */
);
/* Fall back to global handler if defined */
if (user->get_handler)
return user->get_handler(
user,
object,
argv[1] /* name */
);
return 0;
}
int __guac_handle_put(guac_user* user, int argc, char** argv) {
guac_object* object;
/* Validate object index */
int object_index = atoi(argv[0]);
if (object_index < 0 || object_index >= GUAC_USER_MAX_OBJECTS)
return 0;
object = &(user->__objects[object_index]);
/* Validate initialization of object */
if (object->index == GUAC_USER_UNDEFINED_OBJECT_INDEX)
return 0;
/* Pull corresponding stream */
int stream_index = atoi(argv[1]);
guac_stream* stream = __init_input_stream(user, stream_index);
if (stream == NULL)
return 0;
/* Call object handler if defined */
if (object->put_handler)
return object->put_handler(
user,
object,
stream,
argv[2], /* mimetype */
argv[3] /* name */
);
/* Fall back to global handler if defined */
if (user->put_handler)
return user->put_handler(
user,
object,
stream,
argv[2], /* mimetype */
argv[3] /* name */
);
/* Otherwise, abort */
guac_protocol_send_ack(user->socket, stream,
"Object write unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED);
return 0;
}
int __guac_handle_disconnect(guac_user* user, int argc, char** argv) {
guac_user_stop(user);
return 0;
}

View File

@ -21,26 +21,41 @@
*/
#ifndef _GUAC_CLIENT_HANDLERS__H
#define _GUAC_CLIENT_HANDLERS__H
#ifndef _GUAC_USER_HANDLERS__H
#define _GUAC_USER_HANDLERS__H
/**
* Provides initial handler functions and a lookup structure for automatically
* handling client instructions. This is used only internally within libguac,
* and is not installed along with the library.
* handling instructions received from each user. This is used only internally
* within libguac, and is not installed along with the library.
*
* @file client-handlers.h
* @file user-handlers.h
*/
#include "config.h"
#include "client.h"
#include "instruction.h"
#include "timestamp.h"
/**
* Internal handler for Guacamole instructions.
* Internal handler for Guacamole instructions. Instruction handlers will be
* invoked when their corresponding instructions are received. The mapping
* of instruction opcode to handler is defined by the
* __guac_instruction_handler_map array.
*
* @param user
* The user that sent the instruction.
*
* @param argc
* The number of arguments in argv.
*
* @param argv
* The arguments included with the instruction, excluding the opcode.
*
* @return
* Zero if the instruction was successfully handled, non-zero otherwise.
*/
typedef int __guac_instruction_handler(guac_client* client, guac_instruction* copied);
typedef int __guac_instruction_handler(guac_user* user, int argc, char** argv);
/**
* Structure mapping an instruction opcode to an instruction handler.
@ -64,91 +79,92 @@ typedef struct __guac_instruction_handler_mapping {
* is received, this handler will be called. Sync instructions are automatically
* handled, thus there is no client handler for sync instruction.
*/
int __guac_handle_sync(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_sync;
/**
* Internal initial handler for the mouse instruction. When a mouse instruction
* is received, this handler will be called. The client's mouse handler will
* be invoked if defined.
*/
int __guac_handle_mouse(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_mouse;
/**
* Internal initial handler for the key instruction. When a key instruction
* is received, this handler will be called. The client's key handler will
* be invoked if defined.
*/
int __guac_handle_key(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_key;
/**
* Internal initial handler for the clipboard instruction. When a clipboard instruction
* is received, this handler will be called. The client's clipboard handler will
* be invoked if defined.
* Internal initial handler for the clipboard instruction. When a clipboard
* instruction is received, this handler will be called. The client's clipboard
* handler will be invoked if defined.
*/
int __guac_handle_clipboard(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_clipboard;
/**
* Internal initial handler for the file instruction. When a file instruction
* is received, this handler will be called. The client's file handler will
* be invoked if defined.
*/
int __guac_handle_file(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_file;
/**
* Internal initial handler for the pipe instruction. When a pipe instruction
* is received, this handler will be called. The client's pipe handler will
* be invoked if defined.
*/
int __guac_handle_pipe(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_pipe;
/**
* Internal initial handler for the ack instruction. When a ack instruction
* is received, this handler will be called. The client's ack handler will
* be invoked if defined.
*/
int __guac_handle_ack(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_ack;
/**
* Internal initial handler for the blob instruction. When a blob instruction
* is received, this handler will be called. The client's blob handler will
* be invoked if defined.
*/
int __guac_handle_blob(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_blob;
/**
* Internal initial handler for the end instruction. When a end instruction
* is received, this handler will be called. The client's end handler will
* be invoked if defined.
*/
int __guac_handle_end(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_end;
/**
* Internal initial handler for the get instruction. When a get instruction
* is received, this handler will be called. The client's get handler will
* be invoked if defined.
*/
int __guac_handle_get(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_get;
/**
* Internal initial handler for the put instruction. When a put instruction
* is received, this handler will be called. The client's put handler will
* be invoked if defined.
*/
int __guac_handle_put(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_put;
/**
* Internal initial handler for the size instruction. When a size instruction
* is received, this handler will be called. The client's size handler will
* be invoked if defined.
*/
int __guac_handle_size(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_size;
/**
* Internal initial handler for the disconnect instruction. When a disconnect instruction
* is received, this handler will be called. Disconnect instructions are automatically
* handled, thus there is no client handler for disconnect instruction.
* Internal initial handler for the disconnect instruction. When a disconnect
* instruction is received, this handler will be called. Disconnect
* instructions are automatically handled, thus there is no client handler for
* disconnect instruction.
*/
int __guac_handle_disconnect(guac_client* client, guac_instruction* instruction);
__guac_instruction_handler __guac_handle_disconnect;
/**
* Instruction handler mapping table. This is a NULL-terminated array of

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

View File

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

View File

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

View File

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

View File

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

View File

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