diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 6ac3eb21..f6298a33 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -41,6 +41,8 @@ libguacinc_HEADERS = \ guacamole/instruction-types.h \ guacamole/layer.h \ guacamole/layer-types.h \ + guacamole/object.h \ + guacamole/object-types.h \ guacamole/plugin-constants.h \ guacamole/plugin.h \ guacamole/plugin-types.h \ diff --git a/src/libguac/client-handlers.c b/src/libguac/client-handlers.c index 8bbac2c5..fc965aa8 100644 --- a/src/libguac/client-handlers.c +++ b/src/libguac/client-handlers.c @@ -25,6 +25,7 @@ #include "client.h" #include "client-handlers.h" #include "instruction.h" +#include "object.h" #include "protocol.h" #include "stream.h" #include "timestamp.h" @@ -47,6 +48,8 @@ __guac_instruction_handler_mapping __guac_instruction_handler_map[] = { {"ack", __guac_handle_ack}, {"blob", __guac_handle_blob}, {"end", __guac_handle_end}, + {"get", __guac_handle_get}, + {"put", __guac_handle_put}, {NULL, NULL} }; @@ -319,6 +322,77 @@ int __guac_handle_end(guac_client* client, guac_instruction* instruction) { return result; } +int __guac_handle_get(guac_client* client, guac_instruction* instruction) { + + guac_object* object; + + /* Validate object index */ + int object_index = atoi(instruction->argv[0]); + if (object_index < 0 || object_index >= GUAC_CLIENT_MAX_OBJECTS) + return 0; + + object = &(client->__objects[object_index]); + + /* Validate initialization of object */ + if (object->index == GUAC_CLIENT_UNDEFINED_OBJECT_INDEX) + return 0; + + /* Call object handler if defined */ + if (object->get_handler) + return object->get_handler(client, object, + instruction->argv[1] /* name */ + ); + + /* Fall back to global handler if defined */ + if (client->get_handler) + return client->get_handler(client, object, + instruction->argv[1] /* name */ + ); + + return 0; +} + +int __guac_handle_put(guac_client* client, guac_instruction* instruction) { + + guac_object* object; + + /* Validate object index */ + int object_index = atoi(instruction->argv[0]); + if (object_index < 0 || object_index >= GUAC_CLIENT_MAX_OBJECTS) + return 0; + + object = &(client->__objects[object_index]); + + /* Validate initialization of object */ + if (object->index == GUAC_CLIENT_UNDEFINED_OBJECT_INDEX) + return 0; + + /* Pull corresponding stream */ + int stream_index = atoi(instruction->argv[1]); + guac_stream* stream = __init_input_stream(client, stream_index); + if (stream == NULL) + return 0; + + /* Call object handler if defined */ + if (object->put_handler) + return object->put_handler(client, object, stream, + instruction->argv[2], /* mimetype */ + instruction->argv[3] /* name */ + ); + + /* Fall back to global handler if defined */ + if (client->put_handler) + return client->put_handler(client, object, stream, + instruction->argv[2], /* mimetype */ + instruction->argv[3] /* name */ + ); + + /* Otherwise, abort */ + guac_protocol_send_ack(client->socket, stream, + "Object write unsupported", GUAC_PROTOCOL_STATUS_UNSUPPORTED); + return 0; +} + int __guac_handle_disconnect(guac_client* client, guac_instruction* instruction) { guac_client_stop(client); return 0; diff --git a/src/libguac/client-handlers.h b/src/libguac/client-handlers.h index 280db4c8..f72a7383 100644 --- a/src/libguac/client-handlers.h +++ b/src/libguac/client-handlers.h @@ -122,6 +122,20 @@ int __guac_handle_blob(guac_client* client, guac_instruction* instruction); */ int __guac_handle_end(guac_client* client, guac_instruction* instruction); +/** + * Internal initial handler for the get instruction. When a get instruction + * is received, this handler will be called. The client's get handler will + * be invoked if defined. + */ +int __guac_handle_get(guac_client* client, guac_instruction* instruction); + +/** + * Internal initial handler for the put instruction. When a put instruction + * is received, this handler will be called. The client's put handler will + * be invoked if defined. + */ +int __guac_handle_put(guac_client* client, guac_instruction* instruction); + /** * Internal initial handler for the size instruction. When a size instruction * is received, this handler will be called. The client's size handler will diff --git a/src/libguac/client.c b/src/libguac/client.c index 521ac49d..2bb9004d 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -27,6 +27,7 @@ #include "error.h" #include "instruction.h" #include "layer.h" +#include "object.h" #include "pool.h" #include "protocol.h" #include "socket.h" @@ -124,6 +125,39 @@ void guac_client_free_stream(guac_client* client, guac_stream* stream) { } +guac_object* guac_client_alloc_object(guac_client* client) { + + 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; + +} + /** * Returns a newly allocated string containing a guaranteed-unique connection * identifier string which is 37 characters long and begins with a '$' @@ -216,7 +250,7 @@ guac_client* guac_client_alloc() { /* Allocate stream pool */ client->__stream_pool = guac_pool_alloc(0); - /* Initialze streams */ + /* Initialize streams */ client->__input_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS); client->__output_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS); @@ -225,6 +259,14 @@ guac_client* guac_client_alloc() { client->__output_streams[i].index = GUAC_CLIENT_CLOSED_STREAM_INDEX; } + /* Allocate object pool */ + client->__object_pool = guac_pool_alloc(0); + + /* Initialize objects */ + client->__objects = malloc(sizeof(guac_object) * GUAC_CLIENT_MAX_OBJECTS); + for (i=0; i__objects[i].index = GUAC_CLIENT_UNDEFINED_OBJECT_INDEX; + return client; } @@ -249,6 +291,12 @@ void guac_client_free(guac_client* client) { /* 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); } diff --git a/src/libguac/guacamole/client-constants.h b/src/libguac/guacamole/client-constants.h index 310d5252..dfa45db6 100644 --- a/src/libguac/guacamole/client-constants.h +++ b/src/libguac/guacamole/client-constants.h @@ -39,6 +39,28 @@ */ #define GUAC_CLIENT_CLOSED_STREAM_INDEX -1 +/** + * The maximum number of objects supported by any one guac_client. + */ +#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" + /** * The flag set in the mouse button mask when the left mouse button is down. */ diff --git a/src/libguac/guacamole/client-fntypes.h b/src/libguac/guacamole/client-fntypes.h index b30feb8f..1a11f35c 100644 --- a/src/libguac/guacamole/client-fntypes.h +++ b/src/libguac/guacamole/client-fntypes.h @@ -31,6 +31,7 @@ */ #include "client-types.h" +#include "object-types.h" #include "protocol-types.h" #include "stream-types.h" @@ -92,6 +93,18 @@ typedef int guac_client_ack_handler(guac_client* client, guac_stream* stream, */ 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. */ diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index c46b7b94..6d0cc85a 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -34,6 +34,7 @@ #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" @@ -366,6 +367,46 @@ 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 @@ -395,6 +436,16 @@ struct guac_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. @@ -552,6 +603,30 @@ guac_stream* guac_client_alloc_stream(guac_client* client); */ 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. + * + * @param client + * The proxy client to allocate the object for. + * + * @return + * The next available object, or a newly allocated object. + */ +guac_object* guac_client_alloc_object(guac_client* client); + +/** + * 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(). + * + * @param client + * The proxy client to return the object to. + * + * @param object + * The object to return to the pool of available object. + */ +void guac_client_free_object(guac_client* client, guac_object* object); + /** * The default Guacamole client layer, layer 0. */ diff --git a/src/libguac/guacamole/object-types.h b/src/libguac/guacamole/object-types.h new file mode 100644 index 00000000..62639e68 --- /dev/null +++ b/src/libguac/guacamole/object-types.h @@ -0,0 +1,38 @@ +/* + * 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 + * 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_OBJECT_TYPES_H +#define GUAC_OBJECT_TYPES_H + +/** + * Type definitions related to Guacamole protocol objects. + * + * @file object-types.h + */ + +/** + * Represents a single object within the Guacamole protocol. + */ +typedef struct guac_object guac_object; + +#endif + diff --git a/src/libguac/guacamole/object.h b/src/libguac/guacamole/object.h new file mode 100644 index 00000000..01be132a --- /dev/null +++ b/src/libguac/guacamole/object.h @@ -0,0 +1,96 @@ +/* + * 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 + * 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_OBJECT_H +#define GUAC_OBJECT_H + +/** + * Provides functions and structures required for allocating and using objects. + * + * @file object.h + */ + +#include "client-fntypes.h" +#include "object-types.h" + +struct guac_object { + + /** + * The index of this object. + */ + int index; + + /** + * Arbitrary data associated with this object. + */ + void* data; + + /** + * 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 some_function(guac_client* client) { + * + * guac_object* object = guac_client_alloc_object(client); + * object->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 some_function(guac_client* client) { + * + * guac_object* object = guac_client_alloc_object(client); + * object->put_handler = put_handler; + * + * } + * @endcode + */ + guac_client_put_handler* put_handler; + +}; + +#endif + diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 905d8191..bf4629a1 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -32,6 +32,7 @@ */ #include "layer-types.h" +#include "object-types.h" #include "protocol-types.h" #include "socket-types.h" #include "stream-types.h" @@ -217,6 +218,75 @@ int guac_protocol_send_select(guac_socket* socket, const char* protocol); */ int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp); +/* OBJECT INSTRUCTIONS */ + +/** + * Sends a body instruction over the given guac_socket connection. + * + * If an error occurs sending the instruction, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param socket + * The guac_socket connection to use. + * + * @param object + * The object to associated with the stream being used. + * + * @param stream + * The stream to use. + * + * @param mimetype + * The mimetype of the data being sent. + * + * @param name + * The name of the stream whose body is being sent, as requested by a "get" + * instruction. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_body(guac_socket* socket, const guac_object* object, + const guac_stream* stream, const char* mimetype, const char* name); + +/** + * Sends a filesystem instruction over the given guac_socket connection. + * + * If an error occurs sending the instruction, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param socket + * The guac_socket connection to use. + * + * @param object + * The object representing the filesystem being exposed. + * + * @param name + * A name describing the filesystem being exposed. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_filesystem(guac_socket* socket, + const guac_object* object, const char* name); + +/** + * Sends an undefine instruction over the given guac_socket connection. + * + * If an error occurs sending the instruction, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param socket + * The guac_socket connection to use. + * + * @param object + * The object being undefined. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_undefine(guac_socket* socket, + const guac_object* object); + /* MEDIA INSTRUCTIONS */ /** diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index a6c2229f..26b2c2d7 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -24,6 +24,7 @@ #include "error.h" #include "layer.h" +#include "object.h" #include "palette.h" #include "protocol.h" #include "socket.h" @@ -463,6 +464,28 @@ int guac_protocol_send_blob(guac_socket* socket, const guac_stream* stream, } +int guac_protocol_send_body(guac_socket* socket, const guac_object* object, + const guac_stream* stream, const char* mimetype, const char* name) { + + int ret_val; + + guac_socket_instruction_begin(socket); + ret_val = + guac_socket_write_string(socket, "4.body,") + || __guac_socket_write_length_int(socket, object->index) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_int(socket, stream->index) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_string(socket, mimetype) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_string(socket, name) + || guac_socket_write_string(socket, ";"); + + guac_socket_instruction_end(socket); + return ret_val; + +} + int guac_protocol_send_cfill(guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int r, int g, int b, int a) { @@ -833,6 +856,24 @@ int guac_protocol_send_file(guac_socket* socket, const guac_stream* stream, } +int guac_protocol_send_filesystem(guac_socket* socket, + const guac_object* object, const char* name) { + + int ret_val; + + guac_socket_instruction_begin(socket); + ret_val = + guac_socket_write_string(socket, "10.filesystem,") + || __guac_socket_write_length_int(socket, object->index) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_string(socket, name) + || guac_socket_write_string(socket, ";"); + + guac_socket_instruction_end(socket); + return ret_val; + +} + int guac_protocol_send_identity(guac_socket* socket, const guac_layer* layer) { int ret_val; @@ -1284,6 +1325,22 @@ int guac_protocol_send_transform(guac_socket* socket, const guac_layer* layer, } +int guac_protocol_send_undefine(guac_socket* socket, + const guac_object* object) { + + int ret_val; + + guac_socket_instruction_begin(socket); + ret_val = + guac_socket_write_string(socket, "8.undefine,") + || __guac_socket_write_length_int(socket, object->index) + || guac_socket_write_string(socket, ";"); + + guac_socket_instruction_end(socket); + return ret_val; + +} + int guac_protocol_send_video(guac_socket* socket, const guac_stream* stream, const guac_layer* layer, const char* mimetype, double duration) {