From a8151c40c4f02ebb15b6ab534e606302aba2543c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 1 Jul 2020 17:46:26 -0700 Subject: [PATCH] GUACAMOLE-221: Implement libguac convenience API for awaiting and processing argument streams. --- src/libguac/Makefile.am | 4 + src/libguac/argv.c | 340 +++++++++++++++++++++++++ src/libguac/guacamole/argv-constants.h | 64 +++++ src/libguac/guacamole/argv-fntypes.h | 58 +++++ src/libguac/guacamole/argv.h | 127 +++++++++ 5 files changed, 593 insertions(+) create mode 100644 src/libguac/argv.c create mode 100644 src/libguac/guacamole/argv-constants.h create mode 100644 src/libguac/guacamole/argv-fntypes.h create mode 100644 src/libguac/guacamole/argv.h diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 9d120e32..50d78a55 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -32,6 +32,9 @@ SUBDIRS = . tests libguacincdir = $(includedir)/guacamole libguacinc_HEADERS = \ + guacamole/argv.h \ + guacamole/argv-constants.h \ + guacamole/argv-fntypes.h \ guacamole/audio.h \ guacamole/audio-fntypes.h \ guacamole/audio-types.h \ @@ -83,6 +86,7 @@ noinst_HEADERS = \ wait-fd.h libguac_la_SOURCES = \ + argv.c \ audio.c \ client.c \ encode-jpeg.c \ diff --git a/src/libguac/argv.c b/src/libguac/argv.c new file mode 100644 index 00000000..893ffffe --- /dev/null +++ b/src/libguac/argv.c @@ -0,0 +1,340 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "guacamole/argv.h" +#include "guacamole/protocol.h" +#include "guacamole/socket.h" +#include "guacamole/stream.h" +#include "guacamole/string.h" +#include "guacamole/user.h" + +#include +#include +#include + +/** + * The state of an argument that will be automatically processed. Note that + * this is distinct from the state of an argument value that is currently being + * processed. Argument value states are dynamically-allocated and scoped by the + * associated guac_stream. + */ +typedef struct guac_argv_state { + + /** + * The name of the argument. + */ + char name[GUAC_ARGV_MAX_NAME_LENGTH]; + + /** + * Whether at least one value for this argument has been received since it + * was registered. + */ + int received; + + /** + * Bitwise OR of all option flags that should affect processing of this + * argument. The only current flag is GUAC_ARGV_OPTION_ONCE. + */ + int options; + + /** + * The callback that should be invoked when a new value for the associated + * argument has been received. If the GUAC_ARGV_OPTION_ONCE flag is set, + * the callback will be invoked at most once. + */ + guac_argv_callback* callback; + + /** + * The arbitrary data that should be passed to the callback. + */ + void* data; + +} guac_argv_state; + +/** + * The current state of automatic processing of "argv" streams. + */ +typedef struct guac_argv_await_state { + + /** + * Whether automatic argument processing has been stopped via a call to + * guac_argv_stop(). + */ + int stopped; + + /** + * The total number of arguments registered. + */ + unsigned int num_registered; + + /** + * All registered arguments and their corresponding callbacks. + */ + guac_argv_state registered[GUAC_ARGV_MAX_REGISTERED]; + + /** + * Lock which protects multi-threaded access to this entire state + * structure, including the condition that signals specific modifications + * to the structure. + */ + pthread_mutex_t lock; + + /** + * Condition which is signalled whenever the overall state of "argv" + * processing changes, either through the receipt of a new argument value + * or due to a call to guac_argv_stop(). + */ + pthread_cond_t changed; + +} guac_argv_await_state; + +/** + * The value or current status of a connection parameter received over an + * "argv" stream. + */ +typedef struct guac_argv { + + /** + * The state of the specific setting being updated. + */ + guac_argv_state* state; + + /** + * The mimetype of the data being received. + */ + char mimetype[GUAC_ARGV_MAX_MIMETYPE_LENGTH]; + + /** + * Buffer space for containing the received argument value. + */ + char buffer[GUAC_ARGV_MAX_LENGTH]; + + /** + * The number of bytes received so far. + */ + int length; + +} guac_argv; + +/** + * Statically-allocated, shared state of the guac_argv_*() family of functions. + */ +static guac_argv_await_state await_state = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .changed = PTHREAD_COND_INITIALIZER +}; + +/** + * Returns whether at least one value for each of the provided arguments has + * been received. + * + * @param args + * A NULL-terminated array of the names of all arguments to test. + * + * @return + * Non-zero if at least one value has been received for each of the + * provided arguments, zero otherwise. + */ +static int guac_argv_is_received(const char** args) { + + for (int i = 0; i < await_state.num_registered; i++) { + + /* Ignore all received arguments */ + guac_argv_state* state = &await_state.registered[i]; + if (state->received) + continue; + + /* Fail immediately for any matching non-received arguments */ + for (const char** arg = args; *arg != NULL; arg++) { + if (strcmp(state->name, *(arg++)) == 0) + return 0; + } + + } + + /* All arguments were received */ + return 1; + +} + +int guac_argv_register(const char* name, guac_argv_callback* callback, void* data, int options) { + + pthread_mutex_lock(&await_state.lock); + + if (await_state.num_registered == GUAC_ARGV_MAX_REGISTERED) { + pthread_mutex_unlock(&await_state.lock); + return 1; + } + + guac_argv_state* state = &await_state.registered[await_state.num_registered++]; + guac_strlcpy(state->name, name, sizeof(state->name)); + state->options = options; + state->callback = callback; + state->data = data; + + pthread_mutex_unlock(&await_state.lock); + return 0; + +} + +int guac_argv_await(const char** args) { + + /* Wait for all requested arguments to be received (or for receipt to be + * stopped) */ + pthread_mutex_lock(&await_state.lock); + while (!await_state.stopped && !guac_argv_is_received(args)) + pthread_cond_wait(&await_state.changed, &await_state.lock); + + /* Arguments were successfully received only if receipt was not stopped */ + int retval = await_state.stopped; + pthread_mutex_unlock(&await_state.lock); + return retval; + +} + +/** + * Handler for "blob" instructions which appends the data from received blobs + * to the end of the in-progress argument value buffer. + * + * @see guac_user_blob_handler + */ +static int guac_argv_blob_handler(guac_user* user, guac_stream* stream, + void* data, int length) { + + guac_argv* argv = (guac_argv*) stream->data; + + /* Calculate buffer size remaining, including space for null terminator, + * adjusting received length accordingly */ + int remaining = sizeof(argv->buffer) - argv->length - 1; + if (length > remaining) + length = remaining; + + /* Append received data to end of buffer */ + memcpy(argv->buffer + argv->length, data, length); + argv->length += length; + + return 0; + +} + +/** + * Handler for "end" instructions which applies the changes specified by the + * argument value buffer associated with the stream. + * + * @see guac_user_end_handler + */ +static int guac_argv_end_handler(guac_user* user, guac_stream* stream) { + + /* Append null terminator to value */ + guac_argv* argv = (guac_argv*) stream->data; + argv->buffer[argv->length] = '\0'; + + pthread_mutex_lock(&await_state.lock); + + /* Invoke callback, limiting to a single invocation if + * GUAC_ARGV_OPTION_ONCE applies */ + guac_argv_state* state = argv->state; + if (!(state->options & GUAC_ARGV_OPTION_ONCE) || !state->received) { + if (state->callback != NULL) + state->callback(user, argv->mimetype, state->name, argv->buffer, state->data); + } + + /* Notify that argument has been received */ + state->received = 1; + pthread_cond_broadcast(&await_state.changed); + + pthread_mutex_unlock(&await_state.lock); + + free(argv); + return 0; + +} + +int guac_argv_received(guac_stream* stream, const char* mimetype, const char* name) { + + pthread_mutex_lock(&await_state.lock); + + for (int i = 0; i < await_state.num_registered; i++) { + + /* Ignore any arguments that have already been received if they are + * declared as being acceptable only once */ + guac_argv_state* state = &await_state.registered[i]; + if ((state->options & GUAC_ARGV_OPTION_ONCE) && state->received) + continue; + + /* Argument matched */ + if (strcmp(state->name, name) == 0) { + + guac_argv* argv = malloc(sizeof(guac_argv)); + guac_strlcpy(argv->mimetype, mimetype, sizeof(argv->mimetype)); + argv->state = state; + argv->length = 0; + + stream->data = argv; + stream->blob_handler = guac_argv_blob_handler; + stream->end_handler = guac_argv_end_handler; + + pthread_mutex_unlock(&await_state.lock); + return 0; + + } + + } + + /* No such argument awaiting processing */ + pthread_mutex_unlock(&await_state.lock); + return 1; + +} + +void guac_argv_stop() { + pthread_mutex_lock(&await_state.lock); + + /* Signal any waiting threads that no further argument values will be + * received */ + if (!await_state.stopped) { + await_state.stopped = 1; + pthread_cond_broadcast(&await_state.changed); + + } + + pthread_mutex_unlock(&await_state.lock); +} + +int guac_argv_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name) { + + /* Refuse stream if argument is not registered */ + if (guac_argv_received(stream, mimetype, name)) { + guac_protocol_send_ack(user->socket, stream, "Not allowed.", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + /* Signal stream is ready */ + guac_protocol_send_ack(user->socket, stream, "Ready for updated " + "parameter.", GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + return 0; + +} + diff --git a/src/libguac/guacamole/argv-constants.h b/src/libguac/guacamole/argv-constants.h new file mode 100644 index 00000000..88402299 --- /dev/null +++ b/src/libguac/guacamole/argv-constants.h @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_ARGV_CONSTANTS_H +#define GUAC_ARGV_CONSTANTS_H + +/** + * Constants related to automatic handling of received "argv" instructions. + * + * @file argv-constants.h + */ + +/** + * Option flag which declares to guac_argv_register() that the associated + * argument should be processed exactly once. If multiple "argv" streams are + * received for the argument, only the first such stream is processed. + * Additional streams will be rejected. + */ +#define GUAC_ARGV_OPTION_ONCE 1 + +/** + * The maximum number of bytes to allow for any argument value received via an + * argv stream and processed using guac_argv_received(), including null + * terminator. + */ +#define GUAC_ARGV_MAX_LENGTH 16384 + +/** + * The maximum number of bytes to allow within the name of any argument + * registered with guac_argv_register(), including null terminator. + */ +#define GUAC_ARGV_MAX_NAME_LENGTH 256 + +/** + * The maximum number of bytes to allow within the mimetype of any received + * argument value passed to a callback registered with guac_argv_register(), + * including null terminator. + */ +#define GUAC_ARGV_MAX_MIMETYPE_LENGTH 4096 + +/** + * The maximum number of arguments that may be registered via guac_argv_await() + * or guac_argv_await_async() before further argument registrations will fail. + */ +#define GUAC_ARGV_MAX_REGISTERED 128 + +#endif + diff --git a/src/libguac/guacamole/argv-fntypes.h b/src/libguac/guacamole/argv-fntypes.h new file mode 100644 index 00000000..d8c49e60 --- /dev/null +++ b/src/libguac/guacamole/argv-fntypes.h @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_ARGV_FNTYPES_H +#define GUAC_ARGV_FNTYPES_H + +/** + * Function type definitions related to automatic handling of received "argv" + * instructions. + * + * @file argv-fntypes.h + */ + +#include "user-types.h" + +/** + * Callback which is invoked by the automatic "argv" handling when the full + * value of a received argument has been received. + * + * @param user + * The user that opened the argument value stream. + * + * @param mimetype + * The mimetype of the data that will be sent along the stream. + * + * @param name + * The name of the connection parameter being updated. It is up to the + * implementation of this handler to decide whether and how to update a + * connection parameter. + * + * @param value + * The value of the received argument. + * + * @param data + * Any arbitrary data that was provided when the received argument was + * registered with guac_argv_register(). + */ +typedef void guac_argv_callback(guac_user* user, const char* mimetype, + const char* name, const char* value, void* data); + +#endif + diff --git a/src/libguac/guacamole/argv.h b/src/libguac/guacamole/argv.h new file mode 100644 index 00000000..e2d60921 --- /dev/null +++ b/src/libguac/guacamole/argv.h @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_ARGV_H +#define GUAC_ARGV_H + +/** + * Convenience functions for processing parameter values that are submitted + * dynamically using "argv" instructions. + * + * @file argv.h + */ + +#include "argv-constants.h" +#include "argv-fntypes.h" +#include "stream-types.h" +#include "user-fntypes.h" + +/** + * Registers the given callback such that it is automatically invoked when an + * "argv" stream for an argument having the given name is processed using + * guac_argv_received(). The maximum number of arguments that may be registered + * in this way is limited by GUAC_ARGV_MAX_REGISTERED. The maximum length of + * any provided argument name is limited by GUAC_ARGV_MAX_NAME_LENGTH. + * + * @see GUAC_ARGV_MAX_NAME_LENGTH + * @see GUAC_ARGV_MAX_REGISTERED + * + * @see GUAC_ARGV_OPTION_ONCE + * + * @param name + * The name of the argument that should be handled by the given callback. + * + * @param callback + * The callback to invoke when the value of an argument having the given + * name has finished being received. + * + * @param data + * Arbitrary data to be passed to the given callback when a value is + * received for the given argument. + * + * @param options + * Bitwise OR of all option flags that should affect processing of this + * argument. The only current flag is GUAC_ARGV_OPTION_ONCE. + * + * @return + * Zero if the callback was successfully registered, non-zero if the + * maximum number of registered callbacks has already been reached. + */ +int guac_argv_register(const char* name, guac_argv_callback* callback, void* data, int options); + +/** + * Waits for receipt of each of the given arguments via guac_argv_received(). + * This function will block until either ALL of the given arguments have been + * received via guac_argv_received() or until automatic processing of received + * arguments is stopped with guac_argv_stop(). + * + * @param args + * A NULL-terminated array of the names of all arguments that this function + * should wait for. + * + * @return + * Zero if all of the specified arguments were received, non-zero if + * guac_argv_stop() was called before all arguments were received. + */ +int guac_argv_await(const char** args); + +/** + * Hands off management of the given guac_stream, automatically processing data + * received over that stream as the value of the argument having the given + * name. The argument must have already been registered with + * guac_argv_register(). The blob_handler and end_handler of the given stream, + * if already set, will be overridden without regard to their current value. + * + * It is the responsibility of the caller to properly send any required "ack" + * instructions to accept or reject the received stream. + * + * @param stream + * The guac_stream that will receive the value of the argument having the + * given name. + * + * @param mimetype + * The mimetype of the data that will be received over the stream. + * + * @param name + * The name of the argument being received. + * + * @return + * Zero if handling of the guac_stream has been successfully handed off, + * non-zero if the provided argument has not yet been registered with + * guac_argv_register(). + */ +int guac_argv_received(guac_stream* stream, const char* mimetype, const char* name); + +/** + * Stops further automatic processing of received "argv" streams. Any call to + * guac_argv_await() that is currently blocking will return, and any future + * calls to guac_argv_await() will return immediately without blocking. + */ +void guac_argv_stop(); + +/** + * Convenience implementation of the "argv" instruction handler which + * automatically sends any required "ack" instructions and invokes + * guac_argv_received(). Only arguments that are registered with + * guac_argv_register() will be processed. + */ +guac_user_argv_handler guac_argv_handler; + +#endif +