From 21ab9d765da8c0b519d6aa7c90813adf4fb9705e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Apr 2016 15:54:26 -0700 Subject: [PATCH 01/25] GUACAMOLE-25: Add stub "guacai" plugin for AUDIO_INPUT channel support. --- src/protocols/rdp/Makefile.am | 38 +++++++++++++ src/protocols/rdp/audio_input.c | 74 ++++++++++++++++++++++++++ src/protocols/rdp/audio_input.h | 54 +++++++++++++++++++ src/protocols/rdp/guac_ai/ai_service.c | 56 +++++++++++++++++++ src/protocols/rdp/guac_ai/ai_service.h | 29 ++++++++++ src/protocols/rdp/rdp.c | 30 +++++++---- src/protocols/rdp/rdp_settings.c | 12 +++++ src/protocols/rdp/rdp_settings.h | 5 ++ src/protocols/rdp/user.c | 5 ++ 9 files changed, 293 insertions(+), 10 deletions(-) create mode 100644 src/protocols/rdp/audio_input.c create mode 100644 src/protocols/rdp/audio_input.h create mode 100644 src/protocols/rdp/guac_ai/ai_service.c create mode 100644 src/protocols/rdp/guac_ai/ai_service.h diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 91593cbb..44f248a3 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -24,6 +24,7 @@ lib_LTLIBRARIES = libguac-client-rdp.la libguac_client_rdp_la_SOURCES = \ _generated_keymaps.c \ + audio_input.c \ client.c \ input.c \ rdp.c \ @@ -44,6 +45,9 @@ libguac_client_rdp_la_SOURCES = \ unicode.c \ user.c +guacai_sources = \ + guac_ai/ai_service.c + guacsvc_sources = \ guac_svc/svc_service.c \ rdp_svc.c @@ -68,6 +72,7 @@ guacdr_sources = \ noinst_HEADERS = \ compat/client-cliprdr.h \ compat/rail.h \ + guac_ai/ai_service.h \ guac_rdpdr/rdpdr_fs_messages.h \ guac_rdpdr/rdpdr_fs_messages_dir_info.h \ guac_rdpdr/rdpdr_fs_messages_file_info.h \ @@ -79,6 +84,7 @@ noinst_HEADERS = \ guac_rdpsnd/rdpsnd_messages.h \ guac_rdpsnd/rdpsnd_service.h \ guac_svc/svc_service.h \ + audio_input.h \ client.h \ input.h \ rdp.h \ @@ -104,6 +110,7 @@ noinst_HEADERS = \ if ! ENABLE_WINPR noinst_HEADERS += compat/winpr-stream.h compat/winpr-wtypes.h libguac_client_rdp_la_SOURCES += compat/winpr-stream.c +guacai_sources += compat/winpr-stream.c guacsvc_sources += compat/winpr-stream.c guacsnd_sources += compat/winpr-stream.c guacdr_sources += compat/winpr-stream.c @@ -148,6 +155,25 @@ guacdr_libadd = \ @COMMON_LTLIB@ \ @LIBGUAC_LTLIB@ +# +# Audio Input +# + +guacai_cflags = \ + -Werror -Wall -Iinclude \ + @COMMON_INCLUDE@ \ + @COMMON_SSH_INCLUDE@ \ + @LIBGUAC_INCLUDE@ + +guacai_ldflags = \ + -module -avoid-version -shared \ + @PTHREAD_LIBS@ \ + @RDP_LIBS@ + +guacai_libadd = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ + # # RDPSND # @@ -224,10 +250,16 @@ if LEGACY_FREERDP_EXTENSIONS # FreeRDP 1.0-style extensions freerdp_LTLIBRARIES = \ + guacai.la \ guacdr.la \ guacsnd.la \ guacsvc.la +guacai_la_SOURCES = ${guacai_sources} +guacai_la_CFLAGS = ${guacai_cflags} +guacai_la_LDFLAGS = ${guacai_ldflags} +guacai_la_LIBADD = ${guacai_libadd} + guacdr_la_SOURCES = ${guacdr_sources} guacdr_la_CFLAGS = ${guacdr_cflags} guacdr_la_LDFLAGS = ${guacdr_ldflags} @@ -247,10 +279,16 @@ else # FreeRDP 1.1 (and hopefully onward) extensions freerdp_LTLIBRARIES = \ + guacai-client.la \ guacdr-client.la \ guacsnd-client.la \ guacsvc-client.la +guacai_client_la_SOURCES = ${guacai_sources} +guacai_client_la_CFLAGS = ${guacai_cflags} +guacai_client_la_LDFLAGS = ${guacai_ldflags} +guacai_client_la_LIBADD = ${guacai_libadd} + guacdr_client_la_SOURCES = ${guacdr_sources} guacdr_client_la_CFLAGS = ${guacdr_cflags} guacdr_client_la_LDFLAGS = ${guacdr_ldflags} diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c new file mode 100644 index 00000000..8233184a --- /dev/null +++ b/src/protocols/rdp/audio_input.c @@ -0,0 +1,74 @@ +/* + * 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 "audio_input.h" + +#include +#include +#include +#include +#include +#include + +#include + +int guac_rdp_audio_handler(guac_user* user, guac_stream* stream, + char* mimetype) { + + /* FIXME: Assuming mimetype of "audio/L16;rate=44100,channels=2" */ + + /* Init stream data */ + stream->blob_handler = guac_rdp_audio_blob_handler; + stream->end_handler = guac_rdp_audio_end_handler; + + guac_protocol_send_ack(user->socket, stream, + "OK", GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + + return 0; + +} + +int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream, + void* data, int length) { + + /* STUB */ + return 0; + +} + +int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) { + + /* STUB */ + return 0; + +} + +void guac_rdp_audio_load_plugin(rdpContext* context) { + + /* Add "AUDIO_INPUT" channel */ + ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); + args->argc = 1; + args->argv = malloc(sizeof(char**) * 1); + args->argv[0] = strdup("guacai"); + freerdp_dynamic_channel_collection_add(context->settings, args); + +} + diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h new file mode 100644 index 00000000..d32cda1e --- /dev/null +++ b/src/protocols/rdp/audio_input.h @@ -0,0 +1,54 @@ +/* + * 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_RDP_AUDIO_INPUT_H +#define GUAC_RDP_AUDIO_INPUT_H + +#include "config.h" + +#include +#include + +/** + * Handler for inbound audio data (audio input). + */ +guac_user_audio_handler guac_rdp_audio_handler; + +/** + * Handler for stream data related to audio input. + */ +guac_user_blob_handler guac_rdp_audio_blob_handler; + +/** + * Handler for end-of-stream related to audio input. + */ +guac_user_end_handler guac_rdp_audio_end_handler; + +/** + * Loads Guacamole's "guacai" plugin for FreeRDP, adding support for the + * "AUDIO_INPUT" dynamic virtual channel. This function must ONLY be called + * after FreeRDP's "drdynvc" virtual channel plugin has been loaded. + * + * @param context + * The rdpContext associated with the active RDP session. + */ +void guac_rdp_audio_load_plugin(rdpContext* context); + +#endif + diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c new file mode 100644 index 00000000..15490a09 --- /dev/null +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -0,0 +1,56 @@ +/* + * 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 "ai_service.h" +#include "rdp.h" + +#include +#include + +#include +#include +#include +#include + +#ifdef ENABLE_WINPR +#include +#else +#include "compat/winpr-stream.h" +#endif + +/** + * Entry point for AUDIO_INPUT dynamic virtual channel. + */ +int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { + + rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints); + freerdp* instance = settings->instance; + rdpContext* context = instance->context; + guac_client* client = ((rdp_freerdp_context*) context)->client; + + /* STUB */ + guac_client_log(client, GUAC_LOG_DEBUG, + "STUB: AUDIO_INPUT DVC (entry point)"); + + return 1; + +} + diff --git a/src/protocols/rdp/guac_ai/ai_service.h b/src/protocols/rdp/guac_ai/ai_service.h new file mode 100644 index 00000000..caa62adb --- /dev/null +++ b/src/protocols/rdp/guac_ai/ai_service.h @@ -0,0 +1,29 @@ +/* + * 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_RDP_AI_SERVICE_H +#define GUAC_RDP_AI_SERVICE_H + +#include "config.h" + +/* STUB */ + +#endif + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index cd3ee231..7859e209 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -19,6 +19,7 @@ #include "config.h" +#include "audio_input.h" #include "client.h" #include "guac_cursor.h" #include "guac_display.h" @@ -223,24 +224,33 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { (pChannelConnectedEventHandler) guac_rdp_channel_connected); #endif -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - /* Load required plugins if display update is enabled */ - if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) { + /* Load DRDYNVC plugin if required */ + if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE + || settings->enable_audio_input) { - /* Load virtual channel management plugin (needed by display update) */ + /* Load virtual channel management plugin */ if (freerdp_channels_load_plugin(channels, instance->settings, "drdynvc", instance->settings)) guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load drdynvc plugin. Display update support " - "will be disabled."); + "Failed to load drdynvc plugin. Display update and audio " + "input support will be disabled."); /* Init display update plugin if "drdynvc" was loaded successfully */ - else - guac_rdp_disp_load_plugin(instance->context); - - } + else { +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + /* Load "disp" plugin for display update */ + if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) + guac_rdp_disp_load_plugin(instance->context); #endif + /* Load "AUDIO_INPUT" plugin for audio input*/ + if (settings->enable_audio_input) + guac_rdp_audio_load_plugin(instance->context); + + } + + } /* end if drdynvc required */ + /* Load clipboard plugin */ if (freerdp_channels_load_plugin(channels, instance->settings, "cliprdr", NULL)) diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index bc1d831b..4f9343c0 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -90,6 +90,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "recording-name", "create-recording-path", "resize-method", + "enable-audio-input", NULL }; @@ -378,6 +379,12 @@ enum RDP_ARGS_IDX { */ IDX_RESIZE_METHOD, + /** + * "true" if audio input (microphone) should be enabled for the RDP + * connection, "false" or blank otherwise. + */ + IDX_ENABLE_AUDIO_INPUT, + RDP_ARGS_COUNT }; @@ -739,6 +746,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, settings->resize_method = GUAC_RESIZE_NONE; } + /* Audio input enable/disable */ + settings->enable_audio_input = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_AUDIO_INPUT, 0); + /* Success */ return settings; diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/rdp_settings.h index 7e3fec94..90c79cee 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/rdp_settings.h @@ -378,6 +378,11 @@ typedef struct guac_rdp_settings { */ guac_rdp_resize_method resize_method; + /** + * Whether audio input (microphone) is enabled. + */ + int enable_audio_input; + } guac_rdp_settings; /** diff --git a/src/protocols/rdp/user.c b/src/protocols/rdp/user.c index af597dbd..ef13205f 100644 --- a/src/protocols/rdp/user.c +++ b/src/protocols/rdp/user.c @@ -19,6 +19,7 @@ #include "config.h" +#include "audio_input.h" #include "input.h" #include "guac_display.h" #include "user.h" @@ -65,6 +66,10 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { return 1; } + /* Handle inbound audio streams if audio input is enabled */ + if (settings->enable_audio_input) + user->audio_handler = guac_rdp_audio_handler; + } /* If not owner, synchronize with current state */ From 36cc9f492fb0424d32aaa041383d8e8ab8e3cd90 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Apr 2016 17:13:39 -0700 Subject: [PATCH 02/25] GUACAMOLE-25: Convert guac_client pointer to/from a string for sake of FreeRDP's DVC API. --- src/protocols/rdp/Makefile.am | 5 ++- src/protocols/rdp/audio_input.c | 5 +++ src/protocols/rdp/guac_ai/ai_service.c | 7 ++-- src/protocols/rdp/ptr_string.c | 55 +++++++++++++++++++++++++ src/protocols/rdp/ptr_string.h | 57 ++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 src/protocols/rdp/ptr_string.c create mode 100644 src/protocols/rdp/ptr_string.h diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 44f248a3..0ac074a2 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -27,6 +27,7 @@ libguac_client_rdp_la_SOURCES = \ audio_input.c \ client.c \ input.c \ + ptr_string.c \ rdp.c \ rdp_bitmap.c \ rdp_cliprdr.c \ @@ -46,7 +47,8 @@ libguac_client_rdp_la_SOURCES = \ user.c guacai_sources = \ - guac_ai/ai_service.c + guac_ai/ai_service.c \ + ptr_string.c guacsvc_sources = \ guac_svc/svc_service.c \ @@ -87,6 +89,7 @@ noinst_HEADERS = \ audio_input.h \ client.h \ input.h \ + ptr_string.h \ rdp.h \ rdp_bitmap.h \ rdp_cliprdr.h \ diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 8233184a..b5c3f968 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -19,6 +19,8 @@ #include "config.h" #include "audio_input.h" +#include "ptr_string.h" +#include "rdp.h" #include #include @@ -63,11 +65,14 @@ int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) { void guac_rdp_audio_load_plugin(rdpContext* context) { + guac_client* client = ((rdp_freerdp_context*) context)->client; + /* Add "AUDIO_INPUT" channel */ ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); args->argc = 1; args->argv = malloc(sizeof(char**) * 1); args->argv[0] = strdup("guacai"); + args->argv[1] = guac_rdp_ptr_to_string(client); freerdp_dynamic_channel_collection_add(context->settings, args); } diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c index 15490a09..c6ff47b1 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -20,6 +20,7 @@ #include "config.h" #include "ai_service.h" +#include "ptr_string.h" #include "rdp.h" #include @@ -41,10 +42,8 @@ */ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { - rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints); - freerdp* instance = settings->instance; - rdpContext* context = instance->context; - guac_client* client = ((rdp_freerdp_context*) context)->client; + ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); + guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]); /* STUB */ guac_client_log(client, GUAC_LOG_DEBUG, diff --git a/src/protocols/rdp/ptr_string.c b/src/protocols/rdp/ptr_string.c new file mode 100644 index 00000000..1e791217 --- /dev/null +++ b/src/protocols/rdp/ptr_string.c @@ -0,0 +1,55 @@ +/* + * 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 "ptr_string.h" + +#include + +#include +#include + +/** + * The maximum number of bytes required to represent a pointer printed using + * printf()'s "%p". This will be the size of the hex prefix ("0x"), null + * terminator, plus two bytes for every byte required by a pointer. + */ +#define GUAC_RDP_PTR_STRING_LENGTH (sizeof("0x") + (sizeof(void*) * 2)) + +char* guac_rdp_ptr_to_string(void* data) { + + /* Convert pointer to string */ + char* str = malloc(GUAC_RDP_PTR_STRING_LENGTH); + sprintf(str, "%p", data); + + return str; + +} + +void* guac_rdp_string_to_ptr(const char* str) { + + void* data; + + /* Convert string to pointer */ + sscanf(str, "%p", &data); + + return data; + +} + diff --git a/src/protocols/rdp/ptr_string.h b/src/protocols/rdp/ptr_string.h new file mode 100644 index 00000000..6b277210 --- /dev/null +++ b/src/protocols/rdp/ptr_string.h @@ -0,0 +1,57 @@ +/* + * 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_RDP_PTR_STRING_H +#define GUAC_RDP_PTR_STRING_H + +#include "config.h" + +#include + +/** + * Converts the given string back into a void pointer. The string MUST have + * been produced via guac_rdp_ptr_to_string(). + * + * @param str + * The string to convert back to a pointer. + * + * @return + * The pointer value of the given string, as originally passed to + * guac_rdp_ptr_to_string(). + */ +void* guac_rdp_string_to_ptr(const char* str); + +/** + * Converts a void pointer into a string representation, safe for use with + * parts of the FreeRDP API which provide only for passing arbitrary strings, + * despite being within the same memory area. The returned string must + * eventually be freed with a call to free(). + * + * @param data + * The void pointer to convert to a string. + * + * @return + * A newly-allocated string containing the string representation of the + * given void pointer. This string must eventually be freed with a call to + * free(). + */ +char* guac_rdp_ptr_to_string(void* data); + +#endif + From 4fdcfebf2548161d771e430bead6ce7c29a3fc98 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Apr 2016 21:42:31 -0700 Subject: [PATCH 03/25] GUACAMOLE-25: Set AudioCapture flag if audio input is enabled. --- configure.ac | 12 ++++++++++++ src/protocols/rdp/rdp_settings.c | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/configure.ac b/configure.ac index edd49b84..485c10d3 100644 --- a/configure.ac +++ b/configure.ac @@ -436,6 +436,7 @@ then have_freerdp=yes legacy_freerdp_extensions=no rdpsettings_interface=unknown + rdpsettings_audiocapture=yes rdpsettings_audioplayback=yes rdpsettings_deviceredirection=yes freerdp_interface=unknown @@ -686,6 +687,11 @@ then [rdpsettings_audioplayback=no], [[#include ]]) + # Legacy interface may not have AudioCapture settings + AC_CHECK_MEMBERS([rdpSettings.audio_capture],, + [rdpsettings_audiocapture=no], + [[#include ]]) + # Legacy interface may not have DeviceRedirection settings AC_CHECK_MEMBERS([rdpSettings.device_redirection],, [rdpsettings_deviceredirection=no], @@ -707,6 +713,12 @@ if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_audioplayback}" = "xyes"; [Whether the rdpSettings structure has AudioPlayback settings]) fi +# Activate audio capture settings if present +if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_audiocapture}" = "xyes"; then + AC_DEFINE([HAVE_RDPSETTINGS_AUDIOCAPTURE],, + [Whether the rdpSettings structure has AudioCapture settings]) +fi + # Activate device redirection settings if present if test "x${have_freerdp}" = "xyes" -a "x${rdpsettings_deviceredirection}" = "xyes"; then AC_DEFINE([HAVE_RDPSETTINGS_DEVICEREDIRECTION],, diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 4f9343c0..82af859b 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -993,6 +993,17 @@ void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp) { #ifdef HAVE_RDPSETTINGS_AUDIOPLAYBACK rdp_settings->AudioPlayback = guac_settings->audio_enabled; #endif +#endif + + /* Audio capture */ +#ifdef LEGACY_RDPSETTINGS +#ifdef HAVE_RDPSETTINGS_AUDIOCAPTURE + rdp_settings->audio_capture = guac_settings->enable_audio_input; +#endif +#else +#ifdef HAVE_RDPSETTINGS_AUDIOCAPTURE + rdp_settings->AudioCapture = guac_settings->enable_audio_input; +#endif #endif /* Device redirection */ From 9cd89e6580615249cb0dbd8773934b871bc0ddb0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Apr 2016 21:44:14 -0700 Subject: [PATCH 04/25] GUACAMOLE-25: Allocate proper number of argument entries. --- src/protocols/rdp/audio_input.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index b5c3f968..9d5d5055 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -69,8 +69,8 @@ void guac_rdp_audio_load_plugin(rdpContext* context) { /* Add "AUDIO_INPUT" channel */ ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); - args->argc = 1; - args->argv = malloc(sizeof(char**) * 1); + args->argc = 2; + args->argv = malloc(sizeof(char**) * 2); args->argv[0] = strdup("guacai"); args->argv[1] = guac_rdp_ptr_to_string(client); freerdp_dynamic_channel_collection_add(context->settings, args); From ac94fd4cd02f819cceb66206d6216e12c3ab93e2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Apr 2016 22:48:27 -0700 Subject: [PATCH 05/25] GUACAMOLE-25: Add stub callbacks for AUDIO_INPUT data handling. --- src/protocols/rdp/guac_ai/ai_service.c | 224 ++++++++++++++++++++++++- src/protocols/rdp/guac_ai/ai_service.h | 77 ++++++++- 2 files changed, 296 insertions(+), 5 deletions(-) diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c index c6ff47b1..5b723546 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -37,6 +37,208 @@ #include "compat/winpr-stream.h" #endif +/** + * Handles the given data received along the AUDIO_INPUT channel of the RDP + * connection associated with the given guac_client. This handler is + * API-independent and is invoked by API-dependent guac_rdp_ai_data callback + * specific to the version of FreeRDP installed. + * + * @param client + * The guac_client associated with RDP connection having the AUDIO_INPUT + * connection along which the given data was received. + * + * @param stream + * The data received along the AUDIO_INPUT channel. + */ +static void guac_rdp_ai_handle_data(guac_client* client, wStream* stream) { + + /* Read message ID from received PDU */ + BYTE message_id; + Stream_Read_UINT8(stream, message_id); + + /* STUB */ + guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT data received: 0x%x", + message_id); + +} + +/** + * Callback which is invoked when data is received along a connection to the + * AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.1 and older. + * + * @param channel_callback + * The IWTSVirtualChannelCallback structure to which this callback was + * originally assigned. + * + * @param size + * The number of bytes received. + * + * @param buffer + * A buffer containing all bytes received. + * + * @return + * Always zero. + */ +static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, + UINT32 size, BYTE* buffer) { + + guac_rdp_ai_channel_callback* ai_channel_callback = + (guac_rdp_ai_channel_callback*) channel_callback; + + /* Invoke generalized (API-independent) data handler */ + wStream* stream = Stream_New(buffer, size); + guac_rdp_ai_handle_data(ai_channel_callback->client, stream); + Stream_Free(stream, FALSE); + + return 0; + +} + +/** + * Callback which is invoked when a connection to the AUDIO_INPUT plugin is + * closed. + * + * @param channel_callback + * The IWTSVirtualChannelCallback structure to which this callback was + * originally assigned. + * + * @return + * Always zero. + */ +static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) { + + guac_rdp_ai_channel_callback* ai_channel_callback = + (guac_rdp_ai_channel_callback*) channel_callback; + + /* Log closure of AUDIO_INPUT channel */ + guac_client_log(ai_channel_callback->client, GUAC_LOG_DEBUG, + "AUDIO_INPUT channel connection closed"); + + free(ai_channel_callback); + return 0; + +} + +/** + * Callback which is invoked when a new connection is received by the + * AUDIO_INPUT plugin. Additional callbacks required to handle received data + * and closure of the connection must be installed at this point. + * + * @param listener_callback + * The IWTSListenerCallback structure associated with the AUDIO_INPUT + * plugin receiving the new connection. + * + * @param channel + * A reference to the IWTSVirtualChannel instance along which data related + * to the AUDIO_INPUT channel should be sent. + * + * @param data + * Absolutely no idea. According to Microsoft's documentation for the + * function prototype on which FreeRDP's API appears to be based: "This + * parameter is not implemented and is reserved for future use." + * + * @param accept + * Pointer to a flag which should be set to TRUE if the connection should + * be accepted or FALSE otherwise. In the case of FreeRDP, this value + * defaults to TRUE, and TRUE absolutely MUST be identically 1 or it will + * be interpreted as FALSE. + * + * @param channel_callback + * A pointer to the location that the new IWTSVirtualChannelCallback + * structure containing the required callbacks should be assigned. + * + * @return + * Always zero. + */ +static int guac_rdp_ai_new_connection( + IWTSListenerCallback* listener_callback, IWTSVirtualChannel* channel, + BYTE* data, int* accept, + IWTSVirtualChannelCallback** channel_callback) { + + guac_rdp_ai_listener_callback* ai_listener_callback = + (guac_rdp_ai_listener_callback*) listener_callback; + + /* Log new AUDIO_INPUT connection */ + guac_client_log(ai_listener_callback->client, GUAC_LOG_DEBUG, + "New AUDIO_INPUT channel connection"); + + /* Allocate new channel callback */ + guac_rdp_ai_channel_callback* ai_channel_callback = + calloc(1, sizeof(guac_rdp_ai_channel_callback)); + + /* Init listener callback with data from plugin */ + ai_channel_callback->client = ai_listener_callback->client; + ai_channel_callback->parent.OnDataReceived = guac_rdp_ai_data; + ai_channel_callback->parent.OnClose = guac_rdp_ai_close; + + /* Return callback through pointer */ + *channel_callback = (IWTSVirtualChannelCallback*) ai_channel_callback; + return 0; + +} + +/** + * Callback which is invoked when the AUDIO_INPUT plugin has been loaded and + * needs to be initialized with other callbacks and data. + * + * @param plugin + * The AUDIO_INPUT plugin that needs to be initialied. + * + * @param manager + * The IWTSVirtualChannelManager instance with which the AUDIO_INPUT plugin + * must be registered. + * + * @return + * Always zero. + */ +static int guac_rdp_ai_initialize(IWTSPlugin* plugin, + IWTSVirtualChannelManager* manager) { + + /* Allocate new listener callback */ + guac_rdp_ai_listener_callback* ai_listener_callback = + calloc(1, sizeof(guac_rdp_ai_listener_callback)); + + /* Ensure listener callback is freed when plugin is terminated */ + guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin; + ai_plugin->listener_callback = ai_listener_callback; + + /* Init listener callback with data from plugin */ + ai_listener_callback->client = ai_plugin->client; + ai_listener_callback->parent.OnNewChannelConnection = + guac_rdp_ai_new_connection; + + /* Register listener for "AUDIO_INPUT" channel */ + manager->CreateListener(manager, "AUDIO_INPUT", 0, + (IWTSListenerCallback*) ai_listener_callback, NULL); + + return 0; + +} + +/** + * Callback which is invoked when all connections to the AUDIO_INPUT plugin + * have closed and the plugin is being unloaded. + * + * @param plugin + * The AUDIO_INPUT plugin being unloaded. + * + * @return + * Always zero. + */ +static int guac_rdp_ai_terminated(IWTSPlugin* plugin) { + + guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin; + guac_client* client = ai_plugin->client; + + /* Free all non-FreeRDP data */ + free(ai_plugin->listener_callback); + free(ai_plugin); + + guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin unloaded."); + return 0; + +} + /** * Entry point for AUDIO_INPUT dynamic virtual channel. */ @@ -45,9 +247,25 @@ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]); - /* STUB */ - guac_client_log(client, GUAC_LOG_DEBUG, - "STUB: AUDIO_INPUT DVC (entry point)"); + /* Pull previously-allocated plugin */ + guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) + pEntryPoints->GetPlugin(pEntryPoints, "guacai"); + + /* If no such plugin allocated, allocate and register it now */ + if (ai_plugin == NULL) { + + /* Init plugin callbacks and data */ + ai_plugin = calloc(1, sizeof(guac_rdp_ai_plugin)); + ai_plugin->parent.Initialize = guac_rdp_ai_initialize; + ai_plugin->parent.Terminated = guac_rdp_ai_terminated; + ai_plugin->client = client; + + /* Register plugin as "guacai" for later retrieval */ + pEntryPoints->RegisterPlugin(pEntryPoints, "guacai", + (IWTSPlugin*) ai_plugin); + + guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin loaded."); + } return 1; diff --git a/src/protocols/rdp/guac_ai/ai_service.h b/src/protocols/rdp/guac_ai/ai_service.h index caa62adb..4377812a 100644 --- a/src/protocols/rdp/guac_ai/ai_service.h +++ b/src/protocols/rdp/guac_ai/ai_service.h @@ -17,13 +17,86 @@ * under the License. */ - #ifndef GUAC_RDP_AI_SERVICE_H #define GUAC_RDP_AI_SERVICE_H #include "config.h" -/* STUB */ +#include +#include +#include +#include + +/** + * Extended version of the IWTSListenerCallback structure, providing additional + * access to Guacamole-specific data. The IWTSListenerCallback provides access + * to callbacks related to the receipt of new connections to the AUDIO_INPUT + * channel. + */ +typedef struct guac_rdp_ai_listener_callback { + + /** + * The parent IWTSListenerCallback structure that this structure extends. + * THIS MEMBER MUST BE FIRST! + */ + IWTSListenerCallback parent; + + /** + * The guac_client instance associated with the RDP connection using the + * AUDIO_INPUT plugin. + */ + guac_client* client; + +} guac_rdp_ai_listener_callback; + +/** + * Extended version of the IWTSVirtualChannelCallback structure, providing + * additional access to Guacamole-specific data. The IWTSVirtualChannelCallback + * provides access to callbacks related to an active connection to the + * AUDIO_INPUT channel, including receipt of data. + */ +typedef struct guac_rdp_ai_channel_callback { + + /** + * The parent IWTSVirtualChannelCallback structure that this structure + * extends. THIS MEMBER MUST BE FIRST! + */ + IWTSVirtualChannelCallback parent; + + /** + * The guac_client instance associated with the RDP connection using the + * AUDIO_INPUT plugin. + */ + guac_client* client; + +} guac_rdp_ai_channel_callback; + +/** + * All data associated with Guacamole's AUDIO_INPUT plugin for FreeRDP. + */ +typedef struct guac_rdp_ai_plugin { + + /** + * The parent IWTSPlugin structure that this structure extends. THIS + * MEMBER MUST BE FIRST! + */ + IWTSPlugin parent; + + /** + * The listener callback structure allocated when the AUDIO_INPUT plugin + * was loaded, if any. If the plugin did not fully load, this will be NULL. + * If non-NULL, this callback structure must be freed when the plugin is + * terminated. + */ + guac_rdp_ai_listener_callback* listener_callback; + + /** + * The guac_client instance associated with the RDP connection using the + * AUDIO_INPUT plugin. + */ + guac_client* client; + +} guac_rdp_ai_plugin; #endif From 1088332376343c52ca6c66fbd2053146e5b72d22 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 16 Apr 2016 23:28:50 -0700 Subject: [PATCH 06/25] GUACAMOLE-25: Add support for multiple versions of IWTSVirtualChannelCallback. --- configure.ac | 22 +++++++++++++++++++ src/protocols/rdp/guac_ai/ai_service.c | 29 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/configure.ac b/configure.ac index 485c10d3..04cb6bb1 100644 --- a/configure.ac +++ b/configure.ac @@ -764,6 +764,28 @@ then [Whether the legacy rdpBitmap API was found])]) fi +# +# FreeRDP: IWTSVirtualChannelCallback +# + +if test "x${have_freerdp}" = "xyes" +then + AC_MSG_CHECKING([whether IWTSVirtualChannelCallback.OnDataReceived() uses a wStream]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include + #include + #include + int __data_received( + IWTSVirtualChannelCallback* channel_callback, + wStream* stream); + IWTSVirtualChannelCallback cb = { + .OnDataReceived = __data_received + };]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_DEFINE([LEGACY_IWTSVIRTUALCHANNELCALLBACK],, + [Whether the legacy IWTSVirtualChannelCallback API was found])]) +fi + # # FreeRDP: Decompression function variants # diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c index 5b723546..e822f90e 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -62,6 +62,7 @@ static void guac_rdp_ai_handle_data(guac_client* client, wStream* stream) { } +#if LEGACY_IWTSVIRTUALCHANNELCALLBACK /** * Callback which is invoked when data is received along a connection to the * AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.1 and older. @@ -93,6 +94,34 @@ static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, return 0; } +#else +/** + * Callback which is invoked when data is received along a connection to the + * AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.2 and newer. + * + * @param channel_callback + * The IWTSVirtualChannelCallback structure to which this callback was + * originally assigned. + * + * @param stream + * The data received. + * + * @return + * Always zero. + */ +static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, + wStream* stream) { + + guac_rdp_ai_channel_callback* ai_channel_callback = + (guac_rdp_ai_channel_callback*) channel_callback; + + /* Invoke generalized (API-independent) data handler */ + guac_rdp_ai_handle_data(ai_channel_callback->client, stream); + + return 0; + +} +#endif /** * Callback which is invoked when a connection to the AUDIO_INPUT plugin is From 503ffb0d450a8dde8da4faff5fc65551ef4f1376 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 17 Apr 2016 01:00:42 -0700 Subject: [PATCH 07/25] GUACAMOLE-25: Implement message processing. Stub handlers. --- src/protocols/rdp/Makefile.am | 8 +- src/protocols/rdp/guac_ai/ai_messages.c | 84 +++++++++++++ src/protocols/rdp/guac_ai/ai_messages.h | 157 ++++++++++++++++++++++++ src/protocols/rdp/guac_ai/ai_service.c | 47 ++++++- src/protocols/rdp/guac_ai/ai_service.h | 6 + 5 files changed, 293 insertions(+), 9 deletions(-) create mode 100644 src/protocols/rdp/guac_ai/ai_messages.c create mode 100644 src/protocols/rdp/guac_ai/ai_messages.h diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 0ac074a2..0d07a747 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -46,9 +46,10 @@ libguac_client_rdp_la_SOURCES = \ unicode.c \ user.c -guacai_sources = \ - guac_ai/ai_service.c \ - ptr_string.c +guacai_sources = \ + guac_ai/ai_messages.c \ + guac_ai/ai_service.c \ + ptr_string.c guacsvc_sources = \ guac_svc/svc_service.c \ @@ -74,6 +75,7 @@ guacdr_sources = \ noinst_HEADERS = \ compat/client-cliprdr.h \ compat/rail.h \ + guac_ai/ai_messages.h \ guac_ai/ai_service.h \ guac_rdpdr/rdpdr_fs_messages.h \ guac_rdpdr/rdpdr_fs_messages_dir_info.h \ diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/guac_ai/ai_messages.c new file mode 100644 index 00000000..c799a448 --- /dev/null +++ b/src/protocols/rdp/guac_ai/ai_messages.c @@ -0,0 +1,84 @@ +/* + * 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 "ai_messages.h" +#include "rdp.h" + +#include + +#include +#include +#include +#include + +#ifdef ENABLE_WINPR +#include +#else +#include "compat/winpr-stream.h" +#endif + +void guac_rdp_ai_process_version(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream) { + + UINT32 version; + Stream_Read_UINT32(stream, version); + + /* Warn if server's version number is incorrect */ + if (version != 1) + guac_client_log(client, GUAC_LOG_WARNING, + "Server reports AUDIO_INPUT version %i, not 1", version); + + /* Build response version PDU */ + wStream* response = Stream_New(NULL, 5); + Stream_Write_UINT8(response, GUAC_RDP_MSG_SNDIN_VERSION); /* MessageId */ + Stream_Write_UINT32(response, 1); /* Version */ + + /* Send response */ + channel->Write(channel, (UINT32) Stream_GetPosition(response), + Stream_Buffer(response), NULL); + Stream_Free(response, TRUE); + +} + +void guac_rdp_ai_process_formats(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream) { + + /* STUB */ + guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: formats"); + +} + +void guac_rdp_ai_process_open(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream) { + + /* STUB */ + guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: open"); + +} + +void guac_rdp_ai_process_formatchange(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream) { + + /* STUB */ + guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: formatchange"); + +} + diff --git a/src/protocols/rdp/guac_ai/ai_messages.h b/src/protocols/rdp/guac_ai/ai_messages.h new file mode 100644 index 00000000..96282e57 --- /dev/null +++ b/src/protocols/rdp/guac_ai/ai_messages.h @@ -0,0 +1,157 @@ +/* + * 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_RDP_AI_MESSAGES_H +#define GUAC_RDP_AI_MESSAGES_H + +#include "config.h" + +#include +#include + +#ifdef ENABLE_WINPR +#include +#else +#include "compat/winpr-stream.h" +#endif + +/** + * The message ID associated with the AUDIO_INPUT Version PDU. The Version PDU + * is sent by both the client and the server to indicate their version of the + * AUDIO_INPUT channel protocol (which must always be 1). + */ +#define GUAC_RDP_MSG_SNDIN_VERSION 0x01 + +/** + * The message ID associated with the AUDIO_INPUT Sound Formats PDU. The + * Sound Formats PDU is sent by the client and the server to indicate the + * formats of audio supported. + */ +#define GUAC_RDP_MSG_SNDIN_FORMATS 0x02 + +/** + * The message ID associated with the AUDIO_INPUT Open PDU. The Open PDU is + * sent by the server to inform the client that the AUDIO_INPUT channel is + * now open. + */ +#define GUAC_RDP_MSG_SNDIN_OPEN 0x03 + +/** + * The message ID associated with the AUDIO_INPUT Open Reply PDU. The Open + * Reply PDU is sent by the client (after sending a Format Change PDU) to + * acknowledge that the AUDIO_INPUT channel is open. + */ +#define GUAC_RDP_MSG_SNDIN_OPEN_REPLY 0x04 + +/** + * The message ID associated with the AUDIO_INPUT Incoming Data PDU. The + * Incoming Data PDU is sent by the client to inform the server of incoming + * sound format or audio data. + */ +#define GUAC_RDP_MSG_SNDIN_DATA_INCOMING 0x05 + +/** + * The message ID associated with the AUDIO_INPUT Data PDU. The Data PDU is + * sent by the client and contains audio data read from the microphone. + */ +#define GUAC_RDP_MSG_SNDIN_DATA 0x06 + +/** + * The message ID associated with the AUDIO_INPUT Format Change PDU. The Format + * Change PDU is sent by the client to acknowledge the current sound format, or + * by the server to request a different sound format. + */ +#define GUAC_RDP_MSG_SNDIN_FORMATCHANGE 0x07 + +/** + * Processes a Version PDU received from the RDP server. The Version PDU is + * sent by the server to indicate its version of the AUDIO_INPUT channel + * protocol (which must always be 1). + * + * @param client + * The guac_client associated with the current RDP connection. + * + * @param channel + * The IWTSVirtualChannel instance associated with the connected + * AUDIO_INPUT channel. + * + * @param stream + * The received PDU, with the read position just after the message ID field + * common to all AUDIO_INPUT PDUs. + */ +void guac_rdp_ai_process_version(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream); + +/** + * Processes a Sound Formats PDU received from the RDP server. The Sound + * Formats PDU is sent by the server to indicate the formats of audio + * supported. + * + * @param client + * The guac_client associated with the current RDP connection. + * + * @param channel + * The IWTSVirtualChannel instance associated with the connected + * AUDIO_INPUT channel. + * + * @param stream + * The received PDU, with the read position just after the message ID field + * common to all AUDIO_INPUT PDUs. + */ +void guac_rdp_ai_process_formats(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream); + +/** + * Processes a Open PDU received from the RDP server. The Open PDU is sent by + * the server to inform the client that the AUDIO_INPUT channel is now open. + * + * @param client + * The guac_client associated with the current RDP connection. + * + * @param channel + * The IWTSVirtualChannel instance associated with the connected + * AUDIO_INPUT channel. + * + * @param stream + * The received PDU, with the read position just after the message ID field + * common to all AUDIO_INPUT PDUs. + */ +void guac_rdp_ai_process_open(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream); + +/** + * Processes a Format Change PDU received from the RDP server. The Format + * Change PDU is sent by the server to request a different sound format. + * + * @param client + * The guac_client associated with the current RDP connection. + * + * @param channel + * The IWTSVirtualChannel instance associated with the connected + * AUDIO_INPUT channel. + * + * @param stream + * The received PDU, with the read position just after the message ID field + * common to all AUDIO_INPUT PDUs. + */ +void guac_rdp_ai_process_formatchange(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream); + +#endif + diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c index e822f90e..d6538bcd 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -19,6 +19,7 @@ #include "config.h" +#include "ai_messages.h" #include "ai_service.h" #include "ptr_string.h" #include "rdp.h" @@ -47,18 +48,49 @@ * The guac_client associated with RDP connection having the AUDIO_INPUT * connection along which the given data was received. * + * @param channel + * A reference to the IWTSVirtualChannel instance along which responses + * should be sent. + * * @param stream * The data received along the AUDIO_INPUT channel. */ -static void guac_rdp_ai_handle_data(guac_client* client, wStream* stream) { +static void guac_rdp_ai_handle_data(guac_client* client, + IWTSVirtualChannel* channel, wStream* stream) { /* Read message ID from received PDU */ BYTE message_id; Stream_Read_UINT8(stream, message_id); - /* STUB */ - guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT data received: 0x%x", - message_id); + /* Invoke appropriate message processor based on ID */ + switch (message_id) { + + /* Version PDU */ + case GUAC_RDP_MSG_SNDIN_VERSION: + guac_rdp_ai_process_version(client, channel, stream); + break; + + /* Sound Formats PDU */ + case GUAC_RDP_MSG_SNDIN_FORMATS: + guac_rdp_ai_process_formats(client, channel, stream); + break; + + /* Open PDU */ + case GUAC_RDP_MSG_SNDIN_OPEN: + guac_rdp_ai_process_open(client, channel, stream); + break; + + /* Format Change PDU */ + case GUAC_RDP_MSG_SNDIN_FORMATCHANGE: + guac_rdp_ai_process_formatchange(client, channel, stream); + break; + + /* Log unknown message IDs */ + default: + guac_client_log(client, GUAC_LOG_DEBUG, + "Unknown AUDIO_INPUT message ID: 0x%x", message_id); + + } } @@ -85,10 +117,11 @@ static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, guac_rdp_ai_channel_callback* ai_channel_callback = (guac_rdp_ai_channel_callback*) channel_callback; + IWTSVirtualChannel* channel = ai_channel_callback->channel; /* Invoke generalized (API-independent) data handler */ wStream* stream = Stream_New(buffer, size); - guac_rdp_ai_handle_data(ai_channel_callback->client, stream); + guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream); Stream_Free(stream, FALSE); return 0; @@ -114,9 +147,10 @@ static int guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, guac_rdp_ai_channel_callback* ai_channel_callback = (guac_rdp_ai_channel_callback*) channel_callback; + IWTSVirtualChannel* channel = ai_channel_callback->channel; /* Invoke generalized (API-independent) data handler */ - guac_rdp_ai_handle_data(ai_channel_callback->client, stream); + guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream); return 0; @@ -197,6 +231,7 @@ static int guac_rdp_ai_new_connection( /* Init listener callback with data from plugin */ ai_channel_callback->client = ai_listener_callback->client; + ai_channel_callback->channel = channel; ai_channel_callback->parent.OnDataReceived = guac_rdp_ai_data; ai_channel_callback->parent.OnClose = guac_rdp_ai_close; diff --git a/src/protocols/rdp/guac_ai/ai_service.h b/src/protocols/rdp/guac_ai/ai_service.h index 4377812a..28f2227e 100644 --- a/src/protocols/rdp/guac_ai/ai_service.h +++ b/src/protocols/rdp/guac_ai/ai_service.h @@ -63,6 +63,12 @@ typedef struct guac_rdp_ai_channel_callback { */ IWTSVirtualChannelCallback parent; + /** + * The actual virtual channel instance along which the AUDIO_INPUT plugin + * should send any responses. + */ + IWTSVirtualChannel* channel; + /** * The guac_client instance associated with the RDP connection using the * AUDIO_INPUT plugin. From 86806a37592624707aaa7450f6b4fe06bb1cf469 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 17 Apr 2016 02:11:29 -0700 Subject: [PATCH 08/25] GUACAMOLE-25: Handle the server's Sound Formats PDU. --- src/protocols/rdp/guac_ai/ai_messages.c | 147 +++++++++++++++++++++++- src/protocols/rdp/guac_ai/ai_messages.h | 59 ++++++++++ 2 files changed, 204 insertions(+), 2 deletions(-) diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/guac_ai/ai_messages.c index c799a448..d4cc28d0 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.c +++ b/src/protocols/rdp/guac_ai/ai_messages.c @@ -35,6 +35,126 @@ #include "compat/winpr-stream.h" #endif +/** + * Reads AUDIO_FORMAT data from the given stream into the given struct. + * + * @param stream + * The stream to read AUDIO_FORMAT data from. + * + * @param format + * The structure to populate with data from the stream. + */ +static void guac_rdp_ai_read_format(wStream* stream, + guac_rdp_ai_format* format) { + + /* Read audio format into structure */ + Stream_Read_UINT16(stream, format->tag); /* wFormatTag */ + Stream_Read_UINT16(stream, format->channels); /* nChannels */ + Stream_Read_UINT32(stream, format->rate); /* nSamplesPerSec */ + Stream_Read_UINT32(stream, format->bytes_per_sec); /* nAvgBytesPerSec */ + Stream_Read_UINT16(stream, format->block_align); /* nBlockAlign */ + Stream_Read_UINT16(stream, format->bps); /* wBitsPerSample */ + Stream_Read_UINT16(stream, format->data_size); /* cbSize */ + + /* Read arbitrary data block (if applicable) */ + if (format->data_size != 0) { + format->data = Stream_Pointer(stream); /* data */ + Stream_Seek(stream, format->data_size); + } + +} + +/** + * Writes AUDIO_FORMAT data to the given stream from the given struct. + * + * @param stream + * The stream to write AUDIO_FORMAT data to. + * + * @param format + * The structure containing the data that should be written to the stream. + */ +static void guac_rdp_ai_write_format(wStream* stream, + guac_rdp_ai_format* format) { + + /* Write audio format into structure */ + Stream_Write_UINT16(stream, format->tag); /* wFormatTag */ + Stream_Write_UINT16(stream, format->channels); /* nChannels */ + Stream_Write_UINT32(stream, format->rate); /* nSamplesPerSec */ + Stream_Write_UINT32(stream, format->bytes_per_sec); /* nAvgBytesPerSec */ + Stream_Write_UINT16(stream, format->block_align); /* nBlockAlign */ + Stream_Write_UINT16(stream, format->bps); /* wBitsPerSample */ + Stream_Write_UINT16(stream, format->data_size); /* cbSize */ + + /* Write arbitrary data block (if applicable) */ + if (format->data_size != 0) + Stream_Write(stream, format->data, format->data_size); + +} + +/** + * Sends a Data Incoming PDU along the given channel. A Data Incoming PDU is + * used by the client to indicate to the server that format or audio data is + * about to be sent. + * + * @param channel + * The channel along which the PDU should be sent. + */ +static void guac_rdp_ai_send_incoming_data(IWTSVirtualChannel* channel) { + + /* Build response version PDU */ + wStream* response = Stream_New(NULL, 1); + Stream_Write_UINT8(response, GUAC_RDP_MSG_SNDIN_DATA_INCOMING); /* MessageId */ + + /* Send response */ + channel->Write(channel, (UINT32) Stream_GetPosition(response), + Stream_Buffer(response), NULL); + Stream_Free(response, TRUE); + +} + +/** + * Sends a Sound Formats PDU along the given channel. A Sound Formats PDU is + * used by the client to indicate to the server which formats of audio it + * supports (in response to the server sending exactly the same type of PDU). + * This PDU MUST be preceded by the Data Incoming PDU. + * + * @param channel + * The channel along which the PDU should be sent. + * + * @param formats + * An array of all supported formats. + * + * @param num_formats + * The number of entries in the formats array. + */ +static void guac_rdp_ai_send_formats(IWTSVirtualChannel* channel, + guac_rdp_ai_format* formats, int num_formats) { + + int index; + int packet_size = 9; + + /* Calculate packet size */ + for (index = 0; index < num_formats; index++) + packet_size += 18 + formats[index].data_size; + + wStream* stream = Stream_New(NULL, packet_size); + + /* Write header */ + Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_FORMATS); /* MessageId */ + Stream_Write_UINT32(stream, num_formats); /* NumFormats */ + Stream_Write_UINT32(stream, packet_size); /* cbSizeFormatsPacket */ + + /* Write all formats */ + for (index = 0; index < num_formats; index++) + guac_rdp_ai_write_format(stream, &(formats[index])); + + /* Send PDU */ + channel->Write(channel, (UINT32) Stream_GetPosition(stream), + Stream_Buffer(stream), NULL); + Stream_Free(stream, TRUE); + +} + void guac_rdp_ai_process_version(guac_client* client, IWTSVirtualChannel* channel, wStream* stream) { @@ -61,8 +181,31 @@ void guac_rdp_ai_process_version(guac_client* client, void guac_rdp_ai_process_formats(guac_client* client, IWTSVirtualChannel* channel, wStream* stream) { - /* STUB */ - guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: formats"); + UINT32 num_formats; + Stream_Read_UINT32(stream, num_formats); /* NumFormats */ + Stream_Seek_UINT32(stream); /* cbSizeFormatsPacket (MUST BE IGNORED) */ + + UINT32 index; + for (index = 0; index < num_formats; index++) { + + guac_rdp_ai_format format; + guac_rdp_ai_read_format(stream, &format); + + /* Ignore anything but WAVE_FORMAT_PCM */ + if (format.tag != GUAC_RDP_WAVE_FORMAT_PCM) + continue; + + /* Accept single format */ + guac_rdp_ai_send_incoming_data(channel); + guac_rdp_ai_send_formats(channel, &format, 1); + return; + + } + + /* No formats available */ + guac_client_log(client, GUAC_LOG_WARNING, "AUDIO_INPUT: No WAVE format."); + guac_rdp_ai_send_incoming_data(channel); + guac_rdp_ai_send_formats(channel, NULL, 0); } diff --git a/src/protocols/rdp/guac_ai/ai_messages.h b/src/protocols/rdp/guac_ai/ai_messages.h index 96282e57..55cf6e9a 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.h +++ b/src/protocols/rdp/guac_ai/ai_messages.h @@ -31,6 +31,12 @@ #include "compat/winpr-stream.h" #endif +/** + * The format tag associated with raw wave audio (WAVE_FORMAT_PCM). This format + * is required to be supported by all RDP servers. + */ +#define GUAC_RDP_WAVE_FORMAT_PCM 0x01 + /** * The message ID associated with the AUDIO_INPUT Version PDU. The Version PDU * is sent by both the client and the server to indicate their version of the @@ -79,6 +85,59 @@ */ #define GUAC_RDP_MSG_SNDIN_FORMATCHANGE 0x07 +/** + * An AUDIO_INPUT format, analogous to the AUDIO_FORMAT structure defined + * within Microsoft's RDP documentation. + */ +typedef struct guac_rdp_ai_format { + + /** + * The "format tag" denoting the overall format of audio data received, + * such as WAVE_FORMAT_PCM. + */ + UINT16 tag; + + /** + * The number of audio channels. + */ + UINT16 channels; + + /** + * The number of samples per second. + */ + UINT32 rate; + + /** + * The average number of bytes required for one second of audio. + */ + UINT32 bytes_per_sec; + + /** + * The absolute minimum number of bytes required to process audio in this + * format. + */ + UINT16 block_align; + + /** + * The number of bits per sample. + */ + UINT16 bps; + + /** + * The size of the arbitrary data block, if any. The meaning of the data + * within the arbitrary data block is determined by the format tag. + * WAVE_FORMAT_PCM audio has no associated arbitrary data. + */ + UINT16 data_size; + + /** + * Optional arbitrary data whose meaning is determined by the format tag. + * WAVE_FORMAT_PCM audio has no associated arbitrary data. + */ + BYTE* data; + +} guac_rdp_ai_format; + /** * Processes a Version PDU received from the RDP server. The Version PDU is * sent by the server to indicate its version of the AUDIO_INPUT channel From 63cd2ce512a92a31f497d09d5e1903aeab57fd8f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 17 Apr 2016 02:36:32 -0700 Subject: [PATCH 09/25] GUACAMOLE-25: Partially handle the Open PDU. --- src/protocols/rdp/guac_ai/ai_messages.c | 82 ++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/guac_ai/ai_messages.c index d4cc28d0..8b2d0d71 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.c +++ b/src/protocols/rdp/guac_ai/ai_messages.c @@ -101,14 +101,14 @@ static void guac_rdp_ai_write_format(wStream* stream, */ static void guac_rdp_ai_send_incoming_data(IWTSVirtualChannel* channel) { - /* Build response version PDU */ - wStream* response = Stream_New(NULL, 1); - Stream_Write_UINT8(response, GUAC_RDP_MSG_SNDIN_DATA_INCOMING); /* MessageId */ + /* Build data incoming PDU */ + wStream* stream = Stream_New(NULL, 1); + Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_DATA_INCOMING); /* MessageId */ - /* Send response */ - channel->Write(channel, (UINT32) Stream_GetPosition(response), - Stream_Buffer(response), NULL); - Stream_Free(response, TRUE); + /* Send stream */ + channel->Write(channel, (UINT32) Stream_GetPosition(stream), + Stream_Buffer(stream), NULL); + Stream_Free(stream, TRUE); } @@ -155,6 +155,60 @@ static void guac_rdp_ai_send_formats(IWTSVirtualChannel* channel, } +/** + * Sends an Open Reply PDU along the given channel. An Open Reply PDU is + * used by the client to acknowledge the successful opening of the AUDIO_INPUT + * channel. + * + * @param channel + * The channel along which the PDU should be sent. + * + * @param result + * The HRESULT code to send to the server indicating success, failure, etc. + */ +static void guac_rdp_ai_send_open_reply(IWTSVirtualChannel* channel, + UINT32 result) { + + /* Build open reply PDU */ + wStream* stream = Stream_New(NULL, 5); + Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_OPEN_REPLY); /* MessageId */ + Stream_Write_UINT32(stream, result); /* Result */ + + /* Send stream */ + channel->Write(channel, (UINT32) Stream_GetPosition(stream), + Stream_Buffer(stream), NULL); + Stream_Free(stream, TRUE); + +} + +/** + * Sends a Format Change PDU along the given channel. A Format Change PDU is + * used by the client to acknowledge the format being used for data sent + * along the AUDIO_INPUT channel. + * + * @param channel + * The channel along which the PDU should be sent. + * + * @param format + * The index of the format being acknowledged, which must be the index of + * the format within the original Sound Formats PDU received from the + * server. + */ +static void guac_rdp_ai_send_formatchange(IWTSVirtualChannel* channel, + UINT32 format) { + + /* Build format change PDU */ + wStream* stream = Stream_New(NULL, 5); + Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_FORMATCHANGE); /* MessageId */ + Stream_Write_UINT32(stream, format); /* NewFormat */ + + /* Send stream */ + channel->Write(channel, (UINT32) Stream_GetPosition(stream), + Stream_Buffer(stream), NULL); + Stream_Free(stream, TRUE); + +} + void guac_rdp_ai_process_version(guac_client* client, IWTSVirtualChannel* channel, wStream* stream) { @@ -212,8 +266,20 @@ void guac_rdp_ai_process_formats(guac_client* client, void guac_rdp_ai_process_open(guac_client* client, IWTSVirtualChannel* channel, wStream* stream) { + UINT32 packet_frames; + UINT32 initial_format; + + Stream_Read_UINT32(stream, packet_frames); /* FramesPerPacket */ + Stream_Read_UINT32(stream, initial_format); /* InitialFormat */ + /* STUB */ - guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: open"); + guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: open: " + "packet_frames=%i, initial_format=%i", + packet_frames, initial_format); + + /* Success */ + guac_rdp_ai_send_formatchange(channel, initial_format); + guac_rdp_ai_send_open_reply(channel, 0); } From 9d5871a3c8a9af7522beed65d80f7630cc90f7a7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 18 Apr 2016 00:32:51 -0700 Subject: [PATCH 10/25] GUACAMOLE-25: Buffer and send Data PDUs as necessary. --- src/protocols/rdp/Makefile.am | 1 + src/protocols/rdp/audio_input.c | 90 ++++++++++++++++- src/protocols/rdp/audio_input.h | 126 ++++++++++++++++++++++++ src/protocols/rdp/client.c | 5 + src/protocols/rdp/guac_ai/ai_messages.c | 35 ++++++- src/protocols/rdp/guac_ai/ai_service.c | 8 +- src/protocols/rdp/rdp.c | 4 +- src/protocols/rdp/rdp.h | 6 ++ 8 files changed, 270 insertions(+), 5 deletions(-) diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 0d07a747..f76c33ef 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -47,6 +47,7 @@ libguac_client_rdp_la_SOURCES = \ user.c guacai_sources = \ + audio_input.c \ guac_ai/ai_messages.c \ guac_ai/ai_service.c \ ptr_string.c diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 9d5d5055..1bea175b 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -51,14 +51,19 @@ int guac_rdp_audio_handler(guac_user* user, guac_stream* stream, int guac_rdp_audio_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { - /* STUB */ + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Write blob to audio stream, buffering if necessary */ + guac_rdp_audio_buffer_write(rdp_client->audio_input, data, length); + return 0; } int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) { - /* STUB */ + /* Ignore - the AUDIO_INPUT channel will simply not receive anything */ return 0; } @@ -77,3 +82,84 @@ void guac_rdp_audio_load_plugin(rdpContext* context) { } +guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() { + return calloc(1, sizeof(guac_rdp_audio_buffer)); +} + +void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, + int packet_size, guac_rdp_audio_buffer_flush_handler* flush_handler, + void* data) { + + /* Reset buffer state to provided values */ + audio_buffer->bytes_written = 0; + audio_buffer->packet_size = packet_size; + audio_buffer->flush_handler = flush_handler; + audio_buffer->data = data; + + /* Allocate new buffer */ + free(audio_buffer->packet); + audio_buffer->packet = malloc(packet_size); + +} + +void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, + char* buffer, int length) { + + /* Ignore packet if there is no buffer */ + if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) + return; + + /* Continuously write packets until no data remains */ + while (length > 0) { + + /* Calculate ideal size of chunk based on available space */ + int chunk_size = audio_buffer->packet_size + - audio_buffer->bytes_written; + + /* Shrink chunk size if insufficient bytes are provided */ + if (length < chunk_size) + chunk_size = length; + + /* Append buffer */ + memcpy(audio_buffer->packet + audio_buffer->bytes_written, + buffer, chunk_size); + + /* Update byte counters */ + length -= chunk_size; + audio_buffer->bytes_written += chunk_size; + + /* Invoke flush handler if full */ + if (audio_buffer->bytes_written == audio_buffer->packet_size) { + + /* Only actually invoke if defined */ + if (audio_buffer->flush_handler) + audio_buffer->flush_handler(audio_buffer->packet, + audio_buffer->bytes_written, audio_buffer->data); + + /* Reset buffer in all cases */ + audio_buffer->bytes_written = 0; + + } + + } /* end packet write loop */ + +} + +void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) { + + /* Reset buffer state */ + audio_buffer->bytes_written = 0; + audio_buffer->packet_size = 0; + audio_buffer->flush_handler = NULL; + + /* Free packet (if any) */ + free(audio_buffer->packet); + audio_buffer->packet = NULL; + +} + +void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) { + free(audio_buffer->packet); + free(audio_buffer); +} + diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index d32cda1e..c89de153 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -25,6 +25,132 @@ #include #include +/** + * Handler which is invoked when a guac_rdp_audio_buffer's internal packet + * buffer has reached capacity and must be flushed. + * + * @param buffer + * The buffer which needs to be flushed as an audio packet. + * + * @param length + * The number of bytes stored within the buffer. This is guaranteed to be + * identical to the packet_size value specified when the audio buffer was + * initialized. + * + * @param data + * The arbitrary data pointer provided when the audio buffer was + * initialized. + */ +typedef void guac_rdp_audio_buffer_flush_handler(char* buffer, int length, + void* data); + +/** + * A buffer of arbitrary audio data. Received audio data can be written to this + * buffer, and will automatically be flushed via a given handler once the + * internal buffer reaches capacity. + */ +typedef struct guac_rdp_audio_buffer { + + /** + * The size that each audio packet must be, in bytes. The packet buffer + * within this structure will be at least this size. + */ + int packet_size; + + /** + * The number of bytes currently stored within the packet buffer. + */ + int bytes_written; + + /** + * All audio data being prepared for sending to the AUDIO_INPUT channel. + */ + char* packet; + + /** + * Handler function which will be invoked when a full audio packet is + * ready to be flushed to the AUDIO_INPUT channel, if defined. If NULL, + * audio packets will simply be ignored. + */ + guac_rdp_audio_buffer_flush_handler* flush_handler; + + /** + * Arbitrary data assigned by the AUDIO_INPUT plugin implementation. + */ + void* data; + +} guac_rdp_audio_buffer; + +/** + * Allocates a new audio buffer. The new audio buffer will ignore any received + * data until guac_rdp_audio_buffer_begin() is invoked, and will resume + * ignoring received data once guac_rdp_audio_buffer_end() is invoked. + * + * @return + * A newly-allocated audio buffer. + */ +guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(); + +/** + * Begins handling of audio data received via guac_rdp_audio_buffer_write() and + * allocates the necessary underlying packet buffer. Audio packets of exactly + * packet_size bytes will be flushed as available using the provided + * flush_handler. + * + * @param audio_buffer + * The audio buffer to begin. + * + * @param packet_size + * The number of bytes to include in all audio packets provided to the + * given flush_handler. + * + * @param flush_handler + * The function to invoke when an audio packet must be flushed. + * + * @param data + * Arbitrary data to provide to the flush_handler when an audio packet + * needs to be flushed. + */ +void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, + int packet_size, guac_rdp_audio_buffer_flush_handler* flush_handler, + void* data); + +/** + * Writes the given buffer of audio data to the given audio buffer. A new + * packet will be flushed using the associated flush handler once sufficient + * bytes have been accumulated. + * + * @param audio_buffer + * The audio buffer to which the given audio data should be written. + * + * @param buffer + * The buffer of audio data to write to the given audio buffer. + * + * @param length + * The number of bytes to write. + */ +void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, + char* buffer, int length); + +/** + * Stops handling of audio data received via guac_rdp_audio_buffer_write() and + * frees the underlying packet buffer. Further audio data will be ignored until + * guac_rdp_audio_buffer_begin() is invoked again. + * + * @param audio_buffer + * The audio buffer to end. + */ +void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer); + +/** + * Frees the given audio buffer. If guac_rdp_audio_buffer_end() has not yet + * been called, its associated packet buffer will also be freed. + * + * @param audio_buffer + * The audio buffer to free. + */ +void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer); + /** * Handler for inbound audio data (audio input). */ diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 51321257..362f0aad 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -19,6 +19,7 @@ #include "config.h" +#include "audio_input.h" #include "client.h" #include "rdp.h" #include "rdp_disp.h" @@ -129,6 +130,10 @@ int guac_rdp_client_free_handler(guac_client* client) { if (rdp_client->audio != NULL) guac_audio_stream_free(rdp_client->audio); + /* Clean up audio input buffer, if allocated */ + if (rdp_client->audio_input != NULL) + guac_rdp_audio_buffer_free(rdp_client->audio_input); + /* Free client data */ guac_common_clipboard_free(rdp_client->clipboard); free(rdp_client); diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/guac_ai/ai_messages.c index 8b2d0d71..b0eb6cfc 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.c +++ b/src/protocols/rdp/guac_ai/ai_messages.c @@ -20,6 +20,7 @@ #include "config.h" #include "ai_messages.h" +#include "audio_input.h" #include "rdp.h" #include @@ -112,6 +113,21 @@ static void guac_rdp_ai_send_incoming_data(IWTSVirtualChannel* channel) { } +static void guac_rdp_ai_send_data(IWTSVirtualChannel* channel, + char* buffer, int length) { + + /* Build data PDU */ + wStream* stream = Stream_New(NULL, length + 1); + Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_DATA); /* MessageId */ + Stream_Write(stream, buffer, length); /* Data */ + + /* Send stream */ + channel->Write(channel, (UINT32) Stream_GetPosition(stream), + Stream_Buffer(stream), NULL); + Stream_Free(stream, TRUE); + +} + /** * Sends a Sound Formats PDU along the given channel. A Sound Formats PDU is * used by the client to indicate to the server which formats of audio it @@ -263,9 +279,22 @@ void guac_rdp_ai_process_formats(guac_client* client, } +static void guac_rdp_ai_flush_packet(char* buffer, int length, void* data) { + + IWTSVirtualChannel* channel = (IWTSVirtualChannel*) data; + + /* Send data over channel */ + guac_rdp_ai_send_incoming_data(channel); + guac_rdp_ai_send_data(channel, buffer, length); + +} + void guac_rdp_ai_process_open(guac_client* client, IWTSVirtualChannel* channel, wStream* stream) { + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input; + UINT32 packet_frames; UINT32 initial_format; @@ -281,12 +310,16 @@ void guac_rdp_ai_process_open(guac_client* client, guac_rdp_ai_send_formatchange(channel, initial_format); guac_rdp_ai_send_open_reply(channel, 0); + /* FIXME: Assuming mimetype of 16-bit 44100 Hz stereo PCM */ + guac_rdp_audio_buffer_begin(audio_buffer, packet_frames * 2 * 2, + guac_rdp_ai_flush_packet, channel); + } void guac_rdp_ai_process_formatchange(guac_client* client, IWTSVirtualChannel* channel, wStream* stream) { - /* STUB */ + /* STUB: Should not be called as we only accept one format */ guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: formatchange"); } diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c index d6538bcd..945f0398 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -21,6 +21,7 @@ #include "ai_messages.h" #include "ai_service.h" +#include "audio_input.h" #include "ptr_string.h" #include "rdp.h" @@ -173,10 +174,15 @@ static int guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) { guac_rdp_ai_channel_callback* ai_channel_callback = (guac_rdp_ai_channel_callback*) channel_callback; + guac_client* client = ai_channel_callback->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input; + /* Log closure of AUDIO_INPUT channel */ - guac_client_log(ai_channel_callback->client, GUAC_LOG_DEBUG, + guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT channel connection closed"); + guac_rdp_audio_buffer_end(audio_buffer); free(ai_channel_callback); return 0; diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 7859e209..f4613692 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -244,8 +244,10 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { #endif /* Load "AUDIO_INPUT" plugin for audio input*/ - if (settings->enable_audio_input) + if (settings->enable_audio_input) { + rdp_client->audio_input = guac_rdp_audio_buffer_alloc(); guac_rdp_audio_load_plugin(instance->context); + } } diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 9325a183..02d9ac6e 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -22,6 +22,7 @@ #include "config.h" +#include "audio_input.h" #include "guac_clipboard.h" #include "guac_display.h" #include "guac_surface.h" @@ -120,6 +121,11 @@ typedef struct guac_rdp_client { */ guac_audio_stream* audio; + /** + * Audio input buffer, if audio input is enabled. + */ + guac_rdp_audio_buffer* audio_input; + /** * The filesystem being shared, if any. */ From 4e4dbd6a2d9e882a2a23baf758a209621b5334fc Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 20 Apr 2016 22:02:01 -0700 Subject: [PATCH 11/25] GUACAMOLE-25: Fix configure test for FreeRDP variations. --- configure.ac | 6 +++++- src/protocols/rdp/guac_ai/ai_service.c | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 04cb6bb1..7dec629a 100644 --- a/configure.ac +++ b/configure.ac @@ -779,7 +779,11 @@ then wStream* stream); IWTSVirtualChannelCallback cb = { .OnDataReceived = __data_received - };]])], + }; + int main() { + return + cb.OnDataReceived(NULL, NULL); + }]])], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_DEFINE([LEGACY_IWTSVIRTUALCHANNELCALLBACK],, diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c index 945f0398..2fbf2aca 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -95,7 +95,7 @@ static void guac_rdp_ai_handle_data(guac_client* client, } -#if LEGACY_IWTSVIRTUALCHANNELCALLBACK +#ifdef LEGACY_IWTSVIRTUALCHANNELCALLBACK /** * Callback which is invoked when data is received along a connection to the * AUDIO_INPUT plugin. This callback is specific to FreeRDP 1.1 and older. From 166eba11b728ffa4501bea9d0ad7afaf9a755911 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 1 May 2016 00:04:40 -0700 Subject: [PATCH 12/25] GUACAMOLE-25: Fix pointer update in audio input buffer transfer. --- src/protocols/rdp/audio_input.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 1bea175b..8839d35b 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -128,6 +128,9 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, length -= chunk_size; audio_buffer->bytes_written += chunk_size; + /* Advance to next chunk */ + buffer += chunk_size; + /* Invoke flush handler if full */ if (audio_buffer->bytes_written == audio_buffer->packet_size) { From 5030c0603a9d8aa4a65c31afcb20e50ca71136f4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 2 May 2016 00:09:17 -0700 Subject: [PATCH 13/25] GUACAMOLE-25: Add status code reporting the normal closure of a resource. --- src/libguac/guacamole/protocol-types.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libguac/guacamole/protocol-types.h b/src/libguac/guacamole/protocol-types.h index ee9c6683..ea76910d 100644 --- a/src/libguac/guacamole/protocol-types.h +++ b/src/libguac/guacamole/protocol-types.h @@ -87,6 +87,12 @@ typedef enum guac_protocol_status { */ GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT = 0x205, + /** + * The operation could not be performed as the requested resource is now + * closed. + */ + GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED = 0x0206, + /** * The operation could not be performed because bad parameters were * given. From 46bdf0692fca50d74c964701f163cc0df58ba76b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 2 May 2016 00:11:38 -0700 Subject: [PATCH 14/25] GUACAMOLE-25: Delay sending of "ack" until RDP has opened the AUDIO_INPUT stream. Close audio stream with another "ack" when RDP has closed the AUDIO_INPUT stream. --- src/protocols/rdp/audio_input.c | 97 +++++++++++++++++++++++++++++++-- src/protocols/rdp/audio_input.h | 39 +++++++++++++ 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 8839d35b..93c492b9 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -30,19 +30,22 @@ #include #include +#include int guac_rdp_audio_handler(guac_user* user, guac_stream* stream, char* mimetype) { + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + /* FIXME: Assuming mimetype of "audio/L16;rate=44100,channels=2" */ /* Init stream data */ stream->blob_handler = guac_rdp_audio_blob_handler; stream->end_handler = guac_rdp_audio_end_handler; - guac_protocol_send_ack(user->socket, stream, - "OK", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); + /* Associate stream with audio buffer */ + guac_rdp_audio_buffer_set_stream(rdp_client->audio_input, user, stream); return 0; @@ -83,13 +86,72 @@ void guac_rdp_audio_load_plugin(rdpContext* context) { } guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() { - return calloc(1, sizeof(guac_rdp_audio_buffer)); + guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer)); + pthread_mutex_init(&(buffer->lock), NULL); + return buffer; +} + +/** + * Sends an "ack" instruction over the socket associated with the Guacamole + * stream over which audio data is being received. The "ack" instruction will + * only be sent if the Guacamole audio stream has been established (through + * receipt of an "audio" instruction), is still open (has not received an "end" + * instruction nor been associated with an "ack" having an error code), and is + * associated with an active RDP AUDIO_INPUT channel. + * + * @param audio_buffer + * The audio buffer associated with the guac_stream for which the "ack" + * instruction should be sent, if any. If there is no associated + * guac_stream, this function has no effect. + * + * @param message + * An arbitrary human-readable message to send along with the "ack". + * + * @param status + * The Guacamole protocol status code to send with the "ack". This should + * be GUAC_PROTOCOL_STATUS_SUCCESS if the audio stream has been set up + * successfully or GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED if the audio stream + * has been closed (but may usable again if reopened). + */ +static void guac_rdp_audio_buffer_ack(guac_rdp_audio_buffer* audio_buffer, + const char* message, guac_protocol_status status) { + + guac_user* user = audio_buffer->user; + guac_stream* stream = audio_buffer->stream; + + /* Do not send ack unless both sides of the audio stream are ready */ + if (user == NULL || stream == NULL || audio_buffer->packet == NULL) + return; + + /* Send ack instruction */ + guac_protocol_send_ack(user->socket, stream, message, status); + guac_socket_flush(user->socket); + +} + +void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, + guac_user* user, guac_stream* stream) { + + pthread_mutex_lock(&(audio_buffer->lock)); + + /* Associate received stream */ + audio_buffer->user = user; + audio_buffer->stream = stream; + + /* Acknowledge stream creation (if buffer is ready to receive) */ + guac_rdp_audio_buffer_ack(audio_buffer, + "OK", GUAC_PROTOCOL_STATUS_SUCCESS); + + pthread_mutex_unlock(&(audio_buffer->lock)); + } void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, int packet_size, guac_rdp_audio_buffer_flush_handler* flush_handler, void* data) { + pthread_mutex_lock(&(audio_buffer->lock)); + /* Reset buffer state to provided values */ audio_buffer->bytes_written = 0; audio_buffer->packet_size = packet_size; @@ -100,14 +162,24 @@ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, free(audio_buffer->packet); audio_buffer->packet = malloc(packet_size); + /* Acknowledge stream creation (if stream is ready to receive) */ + guac_rdp_audio_buffer_ack(audio_buffer, + "OK", GUAC_PROTOCOL_STATUS_SUCCESS); + + pthread_mutex_unlock(&(audio_buffer->lock)); + } void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, char* buffer, int length) { + pthread_mutex_lock(&(audio_buffer->lock)); + /* Ignore packet if there is no buffer */ - if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) + if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) { + pthread_mutex_unlock(&(audio_buffer->lock)); return; + } /* Continuously write packets until no data remains */ while (length > 0) { @@ -146,10 +218,22 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, } /* end packet write loop */ + pthread_mutex_unlock(&(audio_buffer->lock)); + } void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) { + pthread_mutex_lock(&(audio_buffer->lock)); + + /* The stream is now closed */ + guac_rdp_audio_buffer_ack(audio_buffer, + "CLOSED", GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED); + + /* Unset user and stream */ + audio_buffer->user = NULL; + audio_buffer->stream = NULL; + /* Reset buffer state */ audio_buffer->bytes_written = 0; audio_buffer->packet_size = 0; @@ -159,9 +243,12 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) { free(audio_buffer->packet); audio_buffer->packet = NULL; + pthread_mutex_unlock(&(audio_buffer->lock)); + } void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) { + pthread_mutex_destroy(&(audio_buffer->lock)); free(audio_buffer->packet); free(audio_buffer); } diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index c89de153..776f0a52 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -23,8 +23,11 @@ #include "config.h" #include +#include #include +#include + /** * Handler which is invoked when a guac_rdp_audio_buffer's internal packet * buffer has reached capacity and must be flushed. @@ -51,6 +54,24 @@ typedef void guac_rdp_audio_buffer_flush_handler(char* buffer, int length, */ typedef struct guac_rdp_audio_buffer { + /** + * Lock which is acquired/released to ensure accesses to the audio buffer + * are atomic. + */ + pthread_mutex_t lock; + + /** + * The user from which this audio buffer will receive data. If no user has + * yet opened an associated audio stream, this will be NULL. + */ + guac_user* user; + + /** + * The stream from which this audio buffer will receive data. If no user + * has yet opened an associated audio stream, this will be NULL. + */ + guac_stream* stream; + /** * The size that each audio packet must be, in bytes. The packet buffer * within this structure will be at least this size. @@ -91,6 +112,24 @@ typedef struct guac_rdp_audio_buffer { */ guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(); +/** + * Associates the given audio buffer with the underlying audio stream which + * has been received from the given Guacamole user. Once both the Guacamole + * audio stream and the RDP audio stream are ready, an appropriate "ack" + * message will be sent. + * + * @param audio_buffer + * The audio buffer associated with the audio stream just received. + * + * @param user + * The Guacamole user that created the audio stream. + * + * @param stream + * The guac_stream object representing the audio stream. + */ +void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, + guac_user* user, guac_stream* stream); + /** * Begins handling of audio data received via guac_rdp_audio_buffer_write() and * allocates the necessary underlying packet buffer. Audio packets of exactly From 13933584691f20c7d88294ac41aa6dd4c4f8bf04 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 9 May 2016 18:20:04 -0700 Subject: [PATCH 15/25] GUACAMOLE-25: Abstract DVC management away from FreeRDP for sake of API compatibility. --- src/protocols/rdp/Makefile.am | 2 + src/protocols/rdp/audio_input.c | 10 +-- src/protocols/rdp/audio_input.h | 3 +- src/protocols/rdp/dvc.c | 154 ++++++++++++++++++++++++++++++++ src/protocols/rdp/dvc.h | 138 ++++++++++++++++++++++++++++ src/protocols/rdp/rdp.c | 44 ++++----- src/protocols/rdp/rdp_disp.c | 11 +-- src/protocols/rdp/rdp_disp.h | 3 +- 8 files changed, 323 insertions(+), 42 deletions(-) create mode 100644 src/protocols/rdp/dvc.c create mode 100644 src/protocols/rdp/dvc.h diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index f76c33ef..3669b3dd 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -26,6 +26,7 @@ libguac_client_rdp_la_SOURCES = \ _generated_keymaps.c \ audio_input.c \ client.c \ + dvc.c \ input.c \ ptr_string.c \ rdp.c \ @@ -91,6 +92,7 @@ noinst_HEADERS = \ guac_svc/svc_service.h \ audio_input.h \ client.h \ + dvc.h \ input.h \ ptr_string.h \ rdp.h \ diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 93c492b9..f2fd84a1 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -19,6 +19,7 @@ #include "config.h" #include "audio_input.h" +#include "dvc.h" #include "ptr_string.h" #include "rdp.h" @@ -71,17 +72,12 @@ int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) { } -void guac_rdp_audio_load_plugin(rdpContext* context) { +void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list) { guac_client* client = ((rdp_freerdp_context*) context)->client; /* Add "AUDIO_INPUT" channel */ - ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); - args->argc = 2; - args->argv = malloc(sizeof(char**) * 2); - args->argv[0] = strdup("guacai"); - args->argv[1] = guac_rdp_ptr_to_string(client); - freerdp_dynamic_channel_collection_add(context->settings, args); + guac_rdp_dvc_list_add(list, "guacai", guac_rdp_ptr_to_string(client), NULL); } diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index 776f0a52..e13b41e6 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -21,6 +21,7 @@ #define GUAC_RDP_AUDIO_INPUT_H #include "config.h" +#include "dvc.h" #include #include @@ -213,7 +214,7 @@ guac_user_end_handler guac_rdp_audio_end_handler; * @param context * The rdpContext associated with the active RDP session. */ -void guac_rdp_audio_load_plugin(rdpContext* context); +void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list); #endif diff --git a/src/protocols/rdp/dvc.c b/src/protocols/rdp/dvc.c new file mode 100644 index 00000000..0f0e835a --- /dev/null +++ b/src/protocols/rdp/dvc.c @@ -0,0 +1,154 @@ +/* + * 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 "dvc.h" +#include "guac_list.h" +#include "rdp.h" + +#include +#include +#include + +#include +#include + +guac_rdp_dvc_list* guac_rdp_dvc_list_alloc() { + + guac_rdp_dvc_list* list = malloc(sizeof(guac_rdp_dvc_list)); + + /* Initialize with empty backing list */ + list->channels = guac_common_list_alloc(); + list->channel_count = 0; + + return list; + +} + +void guac_rdp_dvc_list_add(guac_rdp_dvc_list* list, const char* name, ...) { + + va_list args; + + guac_rdp_dvc* dvc = malloc(sizeof(guac_rdp_dvc)); + + va_start(args, name); + + /* Count number of arguments (excluding terminating NULL) */ + dvc->argc = 1; + while (va_arg(args, char*) != NULL) + dvc->argc++; + + /* Reset va_list */ + va_end(args); + va_start(args, name); + + /* Copy argument values into DVC entry */ + dvc->argv = malloc(sizeof(char*) * dvc->argc); + dvc->argv[0] = strdup(name); + int i; + for (i = 1; i < dvc->argc; i++) + dvc->argv[i] = strdup(va_arg(args, char*)); + + va_end(args); + + /* Add entry to DVC list */ + guac_common_list_add(list->channels, dvc); + + /* Update channel count */ + list->channel_count++; + +} + +void guac_rdp_dvc_list_free(guac_rdp_dvc_list* list) { + + /* For each channel */ + guac_common_list_element* current = list->channels->head; + while (current != NULL) { + + /* Free arguments declaration for current channel */ + guac_rdp_dvc* dvc = (guac_rdp_dvc*) current->data; + + /* Free the underlying arguments list if not delegated to FreeRDP */ + if (dvc->argv != NULL) { + + /* Free each argument value */ + for (int i = 0; i < dvc->argc; i++) + free(dvc->argv[i]); + + free(dvc->argv); + } + + free(dvc); + + current = current->next; + + } + + /* Free underlying list */ + guac_common_list_free(list->channels); + + /* Free the DVC list itself */ + free(list); + +} + +int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list) { + + guac_client* client = ((rdp_freerdp_context*) context)->client; + rdpChannels* channels = context->channels; + + /* Skip if no channels will be loaded */ + if (list->channel_count == 0) + return 0; + + /* For each channel */ + guac_common_list_element* current = list->channels->head; + while (current != NULL) { + + /* Get channel arguments */ + guac_rdp_dvc* dvc = (guac_rdp_dvc*) current->data; + current = current->next; + + /* guac_rdp_dvc_list_add() guarantees at one argument */ + assert(dvc->argc >= 1); + + /* guac_rdp_load_drdynvc() MUST only be invoked once */ + assert(dvc->argv != NULL); + + /* Log registration of plugin for current channel */ + guac_client_log(client, GUAC_LOG_DEBUG, + "Registering DVC plugin \"%s\"", dvc->argv[0]); + + /* Register plugin with FreeRDP */ + ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); + args->argc = dvc->argc; + args->argv = dvc->argv; + freerdp_dynamic_channel_collection_add(context->settings, args); + + /* Rely on FreeRDP to free argv storage */ + dvc->argv = NULL; + + } + + /* Load virtual channel management plugin */ + return freerdp_channels_load_plugin(channels, context->settings, + "drdynvc", context->settings); + +} + diff --git a/src/protocols/rdp/dvc.h b/src/protocols/rdp/dvc.h new file mode 100644 index 00000000..02ca6437 --- /dev/null +++ b/src/protocols/rdp/dvc.h @@ -0,0 +1,138 @@ +/* + * 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_RDP_DVC_H +#define GUAC_RDP_DVC_H + +#include "config.h" +#include "guac_list.h" + +#include + +/** + * The set of all arguments that should be passed to a given dynamic virtual + * channel plugin, including the name of that plugin. + */ +typedef struct guac_rdp_dvc { + + /** + * The number of arguments in the argv array. This MUST be at least 1. + */ + int argc; + + /** + * The argument values being passed to the dynamic virtual channel plugin. + * The first entry in this array is always the name of the plugin. If + * guac_rdp_load_drdynvc() has been invoked, and freeing the argument + * values is being delegated to FreeRDP, this will be NULL. + */ + char** argv; + +} guac_rdp_dvc; + +/** + * A list of dynamic virtual channels which should be provided to the DRDYNVC + * plugin once loaded via guac_rdp_load_drdynvc(). This interface exists purely + * to bridge incompatibilities between differing versions of FreeRDP and its + * DRDYNVC plugin. Any allocated guac_rdp_dvc_list is unlikely to be needed + * after the DRDYNVC plugin has been loaded. + */ +typedef struct guac_rdp_dvc_list { + + /** + * Array of all dynamic virtual channels which should be registered with + * the DRDYNVC plugin once loaded. Each list element will point to a + * guac_rdp_dvc structure which must eventually be freed. + */ + guac_common_list* channels; + + /** + * The number of channels within the list. + */ + int channel_count; + +} guac_rdp_dvc_list; + +/** + * Allocates a new, empty list of dynamic virtual channels. New channels may + * be added via guac_rdp_dvc_list_add(). The loading of those channels' + * associated plugins will be deferred until guac_rdp_load_drdynvc() is + * invoked. + * + * @return + * A newly-allocated, empty list of dynamic virtual channels. + */ +guac_rdp_dvc_list* guac_rdp_dvc_list_alloc(); + +/** + * Adds the given dynamic virtual channel plugin name and associated arguments + * to the list. The provied arguments list is NOT optional and MUST be + * NULL-terminated, even if there are no arguments for the named dynamic + * virtual channel plugin. Though FreeRDP requires that the arguments for a + * dynamic virtual channel plugin contain the name of the plugin itself as the + * first argument, the name must be excluded from the arguments provided here. + * This function will automatically take care of adding the plugin name to + * the arguments. + * + * @param list + * The guac_rdp_dvc_list to which the given plugin name and arguments + * should be added, for later bulk registration via + * guac_rdp_load_drdynvc(). + * + * @param name + * The name of the dynamic virtual channel plugin that should be given + * the provided arguments when guac_rdp_load_drdynvc() is invoked. + * + * @param ... + * The string (char*) arguments which should be passed to the dynamic + * virtual channel plugin when it is loaded via guac_rdp_load_drdynvc(), + * excluding the plugin name itself. + */ +void guac_rdp_dvc_list_add(guac_rdp_dvc_list* list, const char* name, ...); + +/** + * Frees the given list of dynamic virtual channels. Note that, while each + * individual entry within this list will be freed, it is partially up to + * FreeRDP to free the storage associated with the arguments passed to the + * virtual channels. + * + * @param list + * The list to free. + */ +void guac_rdp_dvc_list_free(guac_rdp_dvc_list* list); + +/** + * Loads FreeRDP's DRDYNVC plugin and registers the dynamic virtual channel + * plugins described by the given guac_rdp_dvc_list. This function MUST be + * invoked no more than once per RDP connection. Invoking this function + * multiple times, even if the guac_rdp_dvc_list is different each time, will + * result in undefined behavior. + * + * @param context + * The rdpContext associated with the RDP connection for which the DRDYNVC + * plugin should be loaded. + * + * @param list + * A guac_rdp_dvc_list describing the dynamic virtual channel plugins that + * should be registered with the DRDYNVC plugin, along with any arguments. + */ +int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list); + +#endif + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index f4613692..d515fb11 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -21,6 +21,7 @@ #include "audio_input.h" #include "client.h" +#include "dvc.h" #include "guac_cursor.h" #include "guac_display.h" #include "guac_recording.h" @@ -212,6 +213,7 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { rdpPrimaryUpdate* primary; CLRCONV* clrconv; + guac_rdp_dvc_list* dvc_list = guac_rdp_dvc_list_alloc(); #ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER /* Init FreeRDP add-in provider */ @@ -224,34 +226,17 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { (pChannelConnectedEventHandler) guac_rdp_channel_connected); #endif - /* Load DRDYNVC plugin if required */ - if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE - || settings->enable_audio_input) { - - /* Load virtual channel management plugin */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "drdynvc", instance->settings)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load drdynvc plugin. Display update and audio " - "input support will be disabled."); - - /* Init display update plugin if "drdynvc" was loaded successfully */ - else { #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - /* Load "disp" plugin for display update */ - if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) - guac_rdp_disp_load_plugin(instance->context); + /* Load "disp" plugin for display update */ + if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) + guac_rdp_disp_load_plugin(instance->context, dvc_list); #endif - /* Load "AUDIO_INPUT" plugin for audio input*/ - if (settings->enable_audio_input) { - rdp_client->audio_input = guac_rdp_audio_buffer_alloc(); - guac_rdp_audio_load_plugin(instance->context); - } - - } - - } /* end if drdynvc required */ + /* Load "AUDIO_INPUT" plugin for audio input*/ + if (settings->enable_audio_input) { + rdp_client->audio_input = guac_rdp_audio_buffer_alloc(); + guac_rdp_audio_load_plugin(instance->context, dvc_list); + } /* Load clipboard plugin */ if (freerdp_channels_load_plugin(channels, instance->settings, @@ -338,6 +323,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { } + /* Load DRDYNVC plugin if required */ + if (guac_rdp_load_drdynvc(instance->context, dvc_list)) + guac_client_log(client, GUAC_LOG_WARNING, + "Failed to load drdynvc plugin. Display update and audio " + "input support will be disabled."); + + /* Dynamic virtual channel list is no longer needed */ + guac_rdp_dvc_list_free(dvc_list); + /* Init color conversion structure */ clrconv = calloc(1, sizeof(CLRCONV)); clrconv->alpha = 1; diff --git a/src/protocols/rdp/rdp_disp.c b/src/protocols/rdp/rdp_disp.c index eb142867..7c7e059d 100644 --- a/src/protocols/rdp/rdp_disp.c +++ b/src/protocols/rdp/rdp_disp.c @@ -19,6 +19,7 @@ #include "config.h" #include "client.h" +#include "dvc.h" #include "rdp.h" #include "rdp_disp.h" #include "rdp_settings.h" @@ -54,20 +55,14 @@ void guac_rdp_disp_free(guac_rdp_disp* disp) { free(disp); } -void guac_rdp_disp_load_plugin(rdpContext* context) { +void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list) { -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT #ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL context->settings->SupportDisplayControl = TRUE; #endif /* Add "disp" channel */ - ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); - args->argc = 1; - args->argv = malloc(sizeof(char**) * 1); - args->argv[0] = strdup("disp"); - freerdp_dynamic_channel_collection_add(context->settings, args); -#endif + guac_rdp_dvc_list_add(list, "disp", NULL); } diff --git a/src/protocols/rdp/rdp_disp.h b/src/protocols/rdp/rdp_disp.h index 3378cf76..093e7ed4 100644 --- a/src/protocols/rdp/rdp_disp.h +++ b/src/protocols/rdp/rdp_disp.h @@ -20,6 +20,7 @@ #ifndef GUAC_RDP_DISP_H #define GUAC_RDP_DISP_H +#include "dvc.h" #include "rdp_settings.h" #include @@ -102,7 +103,7 @@ void guac_rdp_disp_free(guac_rdp_disp* disp); * * @param context The rdpContext associated with the active RDP session. */ -void guac_rdp_disp_load_plugin(rdpContext* context); +void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list); #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /** From f1d4393eb8cedeec0214c497c73a528806ae2118 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 9 May 2016 22:18:49 -0700 Subject: [PATCH 16/25] GUACAMOLE-25: Implement buffer attachment in Stream_New() and Stream_Free() compatibility functions. --- src/protocols/rdp/compat/winpr-stream.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/protocols/rdp/compat/winpr-stream.c b/src/protocols/rdp/compat/winpr-stream.c index 528177dc..8be45466 100644 --- a/src/protocols/rdp/compat/winpr-stream.c +++ b/src/protocols/rdp/compat/winpr-stream.c @@ -22,17 +22,26 @@ #include "winpr-stream.h" #include "winpr-wtypes.h" -/* - * NOTE: Because the old API did not support local allocation of the buffer - * for each stream, these compatibility implementations ignore - * the parameters of Stream_New() and Stream_Free() that provide them. - */ - wStream* Stream_New(BYTE* buffer, size_t size) { - return stream_new(size); + + /* If no buffer is provided, allocate a new stream of the given size */ + if (buffer == NULL) + return stream_new(size); + + /* Otherwise allocate an empty stream and assign the given buffer */ + wStream* stream = stream_new(0); + stream_attach(stream, buffer, size); + return stream; + } void Stream_Free(wStream* s, BOOL bFreeBuffer) { + + /* Disassociate buffer if it will be freed externally */ + if (!bFreeBuffer) + stream_detach(s); + stream_free(s); + } From 8ccf61e6b1d9198b073b17f5bafe96e12ca65c06 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 9 May 2016 22:23:46 -0700 Subject: [PATCH 17/25] GUACAMOLE-25: Support FreeRDP stable-1.0 and compatible. --- configure.ac | 9 +++++++ src/protocols/rdp/dvc.c | 33 ++++++++++++++++++++++++-- src/protocols/rdp/guac_ai/ai_service.c | 6 +++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 7dec629a..446a36e5 100644 --- a/configure.ac +++ b/configure.ac @@ -579,6 +579,15 @@ then [#include ]) fi +# Availability of ADDIN_ARGV structure for configuring plugins +if test "x${have_freerdp}" = "xyes" +then + AC_CHECK_TYPE([ADDIN_ARGV], + [AC_DEFINE([HAVE_ADDIN_ARGV],, + [Whether the ADDIN_ARGV type is available])],, + [#include ]) +fi + # # FreeRDP: WinPR # diff --git a/src/protocols/rdp/dvc.c b/src/protocols/rdp/dvc.c index 0f0e835a..10c65d7f 100644 --- a/src/protocols/rdp/dvc.c +++ b/src/protocols/rdp/dvc.c @@ -117,6 +117,14 @@ int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list) { if (list->channel_count == 0) return 0; +#ifndef HAVE_ADDIN_ARGV + /* Allocate plugin data array */ + RDP_PLUGIN_DATA* all_plugin_data = + calloc(list->channel_count + 1, sizeof(RDP_PLUGIN_DATA)); + + RDP_PLUGIN_DATA* current_plugin_data = all_plugin_data; +#endif + /* For each channel */ guac_common_list_element* current = list->channels->head; while (current != NULL) { @@ -135,20 +143,41 @@ int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list) { guac_client_log(client, GUAC_LOG_DEBUG, "Registering DVC plugin \"%s\"", dvc->argv[0]); +#ifdef HAVE_ADDIN_ARGV /* Register plugin with FreeRDP */ ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); args->argc = dvc->argc; args->argv = dvc->argv; freerdp_dynamic_channel_collection_add(context->settings, args); +#else + /* Copy all arguments */ + for (int i = 0; i < dvc->argc; i++) + current_plugin_data->data[i] = dvc->argv[i]; + + /* Store size of entry */ + current_plugin_data->size = sizeof(*current_plugin_data); + + /* Advance to next set of plugin data */ + current_plugin_data++; +#endif /* Rely on FreeRDP to free argv storage */ dvc->argv = NULL; } +#ifdef HAVE_ADDIN_ARGV /* Load virtual channel management plugin */ - return freerdp_channels_load_plugin(channels, context->settings, - "drdynvc", context->settings); + return freerdp_channels_load_plugin(channels, context->instance->settings, + "drdynvc", context->instance->settings); +#else + /* Terminate with empty RDP_PLUGIN_DATA element */ + current_plugin_data->size = 0; + + /* Load virtual channel management plugin */ + return freerdp_channels_load_plugin(channels, context->instance->settings, + "drdynvc", all_plugin_data); +#endif } diff --git a/src/protocols/rdp/guac_ai/ai_service.c b/src/protocols/rdp/guac_ai/ai_service.c index 2fbf2aca..5058ea0a 100644 --- a/src/protocols/rdp/guac_ai/ai_service.c +++ b/src/protocols/rdp/guac_ai/ai_service.c @@ -314,8 +314,14 @@ static int guac_rdp_ai_terminated(IWTSPlugin* plugin) { */ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { + /* Pull guac_client from arguments */ +#ifdef HAVE_ADDIN_ARGV ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]); +#else + RDP_PLUGIN_DATA* data = pEntryPoints->GetPluginData(pEntryPoints); + guac_client* client = (guac_client*) guac_rdp_string_to_ptr(data->data[1]); +#endif /* Pull previously-allocated plugin */ guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) From 320f564daf2e67ce2e746b1b5697320dc191815f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 11 May 2016 13:34:29 -0700 Subject: [PATCH 18/25] GUACAMOLE-25: Implement parsing of audio mimetypes. --- src/protocols/rdp/audio_input.c | 114 ++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index f2fd84a1..b7f605d7 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -30,16 +30,130 @@ #include #include +#include #include #include +/** + * Parses the given raw audio mimetype, producing the corresponding rate, + * number of channels, and bytes per sample. + * + * @param mimetype + * The raw auduio mimetype to parse. + * + * @param rate + * A pointer to an int where the sample rate for the PCM format described + * by the given mimetype should be stored. + * + * @param channels + * A pointer to an int where the number of channels used by the PCM format + * described by the given mimetype should be stored. + * + * @param bps + * A pointer to an int where the number of bytes used the PCM format for + * each sample (independent of number of channels) described by the given + * mimetype should be stored. + * + * @return + * Zero if the given mimetype is a raw audio mimetype and has been parsed + * successfully, non-zero otherwise. + */ +static int guac_rdp_audio_parse_mimetype(const char* mimetype, + int* rate, int* channels, int* bps) { + + int parsed_rate = -1; + int parsed_channels = 1; + int parsed_bps; + + /* PCM audio with one byte per sample */ + if (strncmp(mimetype, "audio/L8;", 9) == 0) { + mimetype += 8; /* Advance to semicolon ONLY */ + parsed_bps = 1; + } + + /* PCM audio with two bytes per sample */ + else if (strncmp(mimetype, "audio/L16;", 10) == 0) { + mimetype += 9; /* Advance to semicolon ONLY */ + parsed_bps = 2; + } + + /* Unsupported mimetype */ + else + return 1; + + /* Parse each parameter name/value pair within the mimetype */ + do { + + /* Advance to first character of parameter (current is either a + * semicolon or a comma) */ + mimetype++; + + /* Parse number of channels */ + if (strncmp(mimetype, "channels=", 9) == 0) { + + mimetype += 9; + parsed_channels = strtol(mimetype, (char**) &mimetype, 10); + + /* Fail if value invalid / out of range */ + if (errno == EINVAL || errno == ERANGE) + return 1; + + } + + /* Parse number of rate */ + else if (strncmp(mimetype, "rate=", 5) == 0) { + + mimetype += 5; + parsed_rate = strtol(mimetype, (char**) &mimetype, 10); + + /* Fail if value invalid / out of range */ + if (errno == EINVAL || errno == ERANGE) + return 1; + + } + + /* Advance to next parameter */ + mimetype = strchr(mimetype, ','); + + } while (mimetype != NULL); + + /* Mimetype is invalid if rate was not specified */ + if (rate == -1) + return 1; + + /* Parse success */ + *rate = parsed_rate; + *channels = parsed_channels; + *bps = parsed_bps; + + return 0; + +} + int guac_rdp_audio_handler(guac_user* user, guac_stream* stream, char* mimetype) { guac_client* client = user->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + int rate; + int channels; + int bps; + + /* Parse mimetype, abort on parse error */ + if (guac_rdp_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) { + guac_user_log(user, GUAC_LOG_WARN, "Denying user audio stream with " + "unsupported mimetype: \"%s\"", mimetype); + guac_protocol_send_ack(user->socket, stream, "Unsupported audio " + "mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE); + return 0; + } + /* FIXME: Assuming mimetype of "audio/L16;rate=44100,channels=2" */ + else { + guac_user_log(user, GUAC_LOG_DEBUG, "rate=%i, channels=%i, bps=%i", + rate, channels, bps); + } /* Init stream data */ stream->blob_handler = guac_rdp_audio_blob_handler; From 533a47f06da3559ab66161fe8af8f431add167f0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 11 May 2016 13:48:40 -0700 Subject: [PATCH 19/25] GUACAMOLE-25: Store input and output audio format. --- src/protocols/rdp/audio_input.c | 27 +++++----- src/protocols/rdp/audio_input.h | 69 +++++++++++++++++++++++-- src/protocols/rdp/guac_ai/ai_messages.c | 2 +- 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index b7f605d7..b2f61bdb 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -118,7 +118,7 @@ static int guac_rdp_audio_parse_mimetype(const char* mimetype, } while (mimetype != NULL); /* Mimetype is invalid if rate was not specified */ - if (rate == -1) + if (parsed_rate == -1) return 1; /* Parse success */ @@ -142,25 +142,20 @@ int guac_rdp_audio_handler(guac_user* user, guac_stream* stream, /* Parse mimetype, abort on parse error */ if (guac_rdp_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) { - guac_user_log(user, GUAC_LOG_WARN, "Denying user audio stream with " + guac_user_log(user, GUAC_LOG_WARNING, "Denying user audio stream with " "unsupported mimetype: \"%s\"", mimetype); guac_protocol_send_ack(user->socket, stream, "Unsupported audio " "mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE); return 0; } - /* FIXME: Assuming mimetype of "audio/L16;rate=44100,channels=2" */ - else { - guac_user_log(user, GUAC_LOG_DEBUG, "rate=%i, channels=%i, bps=%i", - rate, channels, bps); - } - /* Init stream data */ stream->blob_handler = guac_rdp_audio_blob_handler; stream->end_handler = guac_rdp_audio_end_handler; /* Associate stream with audio buffer */ - guac_rdp_audio_buffer_set_stream(rdp_client->audio_input, user, stream); + guac_rdp_audio_buffer_set_stream(rdp_client->audio_input, user, stream, + rate, channels, bps); return 0; @@ -240,13 +235,16 @@ static void guac_rdp_audio_buffer_ack(guac_rdp_audio_buffer* audio_buffer, } void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, - guac_user* user, guac_stream* stream) { + guac_user* user, guac_stream* stream, int rate, int channels, int bps) { pthread_mutex_lock(&(audio_buffer->lock)); /* Associate received stream */ audio_buffer->user = user; audio_buffer->stream = stream; + audio_buffer->in_rate = rate; + audio_buffer->in_channels = channels; + audio_buffer->in_bps = bps; /* Acknowledge stream creation (if buffer is ready to receive) */ guac_rdp_audio_buffer_ack(audio_buffer, @@ -257,13 +255,16 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, } void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, - int packet_size, guac_rdp_audio_buffer_flush_handler* flush_handler, - void* data) { + int rate, int channels, int bps, int packet_size, + guac_rdp_audio_buffer_flush_handler* flush_handler, void* data) { pthread_mutex_lock(&(audio_buffer->lock)); /* Reset buffer state to provided values */ audio_buffer->bytes_written = 0; + audio_buffer->out_rate = rate; + audio_buffer->out_channels = channels; + audio_buffer->out_bps = bps; audio_buffer->packet_size = packet_size; audio_buffer->flush_handler = flush_handler; audio_buffer->data = data; @@ -285,6 +286,8 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, pthread_mutex_lock(&(audio_buffer->lock)); + /* FIXME: Assuming mimetype of "audio/L16;rate=44100,channels=2" */ + /* Ignore packet if there is no buffer */ if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) { pthread_mutex_unlock(&(audio_buffer->lock)); diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index e13b41e6..2514b508 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -73,6 +73,45 @@ typedef struct guac_rdp_audio_buffer { */ guac_stream* stream; + /** + * The rate of the audio stream being received from the user, if any, in + * samples per second. If no stream is yet associated, this value is + * undefined. + */ + int in_rate; + + /** + * The number of channels included in the audio stream being received from + * the user, if any. If no stream is yet associated, this value is + * undefined. + */ + int in_channels; + + /** + * The size of each sample within the audio stream being received from the + * user, if any, in bytes. If no stream is yet associated, this value is + * undefined. + */ + int in_bps; + + /** + * The rate of the audio stream expected by RDP, if any, in samples per + * second. If no stream is yet associated, this value is undefined. + */ + int out_rate; + + /** + * The number of channels included in the audio stream expected by RDP, if + * any. If no stream is yet associated, this value is undefined. + */ + int out_channels; + + /** + * The size of each sample within the audio stream expected by RDP, if any, + * in bytes. If no stream is yet associated, this value is undefined. + */ + int out_bps; + /** * The size that each audio packet must be, in bytes. The packet buffer * within this structure will be at least this size. @@ -127,9 +166,21 @@ guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(); * * @param stream * The guac_stream object representing the audio stream. + * + * @param rate + * The rate of the audio stream being received from the user, if any, in + * samples per second. + * + * @param channels + * The number of channels included in the audio stream being received from + * the user, if any. + * + * @param bps + * The size of each sample within the audio stream being received from the + * user, if any, in bytes. */ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, - guac_user* user, guac_stream* stream); + guac_user* user, guac_stream* stream, int rate, int channels, int bps); /** * Begins handling of audio data received via guac_rdp_audio_buffer_write() and @@ -140,6 +191,18 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, * @param audio_buffer * The audio buffer to begin. * + * @param rate + * The rate of the audio stream expected by RDP, if any, in samples per + * second. + * + * @param channels + * The number of channels included in the audio stream expected by RDP, if + * any. + * + * @param bps + * The size of each sample within the audio stream expected by RDP, if any, + * in bytes. + * * @param packet_size * The number of bytes to include in all audio packets provided to the * given flush_handler. @@ -152,8 +215,8 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, * needs to be flushed. */ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, - int packet_size, guac_rdp_audio_buffer_flush_handler* flush_handler, - void* data); + int rate, int channels, int bps, int packet_size, + guac_rdp_audio_buffer_flush_handler* flush_handler, void* data); /** * Writes the given buffer of audio data to the given audio buffer. A new diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/guac_ai/ai_messages.c index b0eb6cfc..520416cd 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.c +++ b/src/protocols/rdp/guac_ai/ai_messages.c @@ -312,7 +312,7 @@ void guac_rdp_ai_process_open(guac_client* client, /* FIXME: Assuming mimetype of 16-bit 44100 Hz stereo PCM */ guac_rdp_audio_buffer_begin(audio_buffer, packet_frames * 2 * 2, - guac_rdp_ai_flush_packet, channel); + 44100, 2, 2, guac_rdp_ai_flush_packet, channel); } From 260d0cd340f78c91dd29acd6de97bf57f2e46cc9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 11 May 2016 14:09:48 -0700 Subject: [PATCH 20/25] GUACAMOLE-25: Remove STUB. --- src/protocols/rdp/guac_ai/ai_messages.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/guac_ai/ai_messages.c index 520416cd..eeb3e287 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.c +++ b/src/protocols/rdp/guac_ai/ai_messages.c @@ -319,8 +319,10 @@ void guac_rdp_ai_process_open(guac_client* client, void guac_rdp_ai_process_formatchange(guac_client* client, IWTSVirtualChannel* channel, wStream* stream) { - /* STUB: Should not be called as we only accept one format */ - guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: formatchange"); + /* Should not be called as we only accept one format */ + guac_client_log(client, GUAC_LOG_DEBUG, + "RDP server requesting AUDIO_INPUT format change despite only one " + "format available."); } From ad00cce0ad882e8e549c74212cf33826ea44c4e1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 25 May 2016 14:38:22 -0700 Subject: [PATCH 21/25] GUACAMOLE-25: Store audio format within common structure. --- src/protocols/rdp/audio_input.c | 12 +++---- src/protocols/rdp/audio_input.h | 62 ++++++++++++++++----------------- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index b2f61bdb..9e88fb5c 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -242,9 +242,9 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, /* Associate received stream */ audio_buffer->user = user; audio_buffer->stream = stream; - audio_buffer->in_rate = rate; - audio_buffer->in_channels = channels; - audio_buffer->in_bps = bps; + audio_buffer->in_format.rate = rate; + audio_buffer->in_format.channels = channels; + audio_buffer->in_format.bps = bps; /* Acknowledge stream creation (if buffer is ready to receive) */ guac_rdp_audio_buffer_ack(audio_buffer, @@ -262,9 +262,9 @@ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, /* Reset buffer state to provided values */ audio_buffer->bytes_written = 0; - audio_buffer->out_rate = rate; - audio_buffer->out_channels = channels; - audio_buffer->out_bps = bps; + audio_buffer->out_format.rate = rate; + audio_buffer->out_format.channels = channels; + audio_buffer->out_format.bps = bps; audio_buffer->packet_size = packet_size; audio_buffer->flush_handler = flush_handler; audio_buffer->data = data; diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index 2514b508..0970671b 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -48,6 +48,29 @@ typedef void guac_rdp_audio_buffer_flush_handler(char* buffer, int length, void* data); +/** + * A description of an arbitrary PCM audio format. + */ +typedef struct guac_rdp_audio_format { + + /** + * The rate of the audio data in samples per second. + */ + int rate; + + /** + * The number of channels included in the audio data. This will be 1 for + * monaural audio and 2 for stereo. + */ + int channels; + + /** + * The size of each sample within the audio data, in bytes. + */ + int bps; + +} guac_rdp_audio_format; + /** * A buffer of arbitrary audio data. Received audio data can be written to this * buffer, and will automatically be flushed via a given handler once the @@ -74,43 +97,18 @@ typedef struct guac_rdp_audio_buffer { guac_stream* stream; /** - * The rate of the audio stream being received from the user, if any, in - * samples per second. If no stream is yet associated, this value is + * The PCM format of the audio stream being received from the user, if any. + * If no stream is yet associated, the values stored within this format are * undefined. */ - int in_rate; + guac_rdp_audio_format in_format; /** - * The number of channels included in the audio stream being received from - * the user, if any. If no stream is yet associated, this value is - * undefined. + * The PCM format of the audio stream expected by RDP, if any. If no audio + * stream has yet been requested by the RDP server, the values stored + * within this format are undefined. */ - int in_channels; - - /** - * The size of each sample within the audio stream being received from the - * user, if any, in bytes. If no stream is yet associated, this value is - * undefined. - */ - int in_bps; - - /** - * The rate of the audio stream expected by RDP, if any, in samples per - * second. If no stream is yet associated, this value is undefined. - */ - int out_rate; - - /** - * The number of channels included in the audio stream expected by RDP, if - * any. If no stream is yet associated, this value is undefined. - */ - int out_channels; - - /** - * The size of each sample within the audio stream expected by RDP, if any, - * in bytes. If no stream is yet associated, this value is undefined. - */ - int out_bps; + guac_rdp_audio_format out_format; /** * The size that each audio packet must be, in bytes. The packet buffer From 1c2890b47c77b324efb6420df43998eff7104333 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 May 2016 10:39:33 -0700 Subject: [PATCH 22/25] GUACAMOLE-25: Store PCM format expected by RDP server. --- src/protocols/rdp/audio_input.c | 35 +++++++++++++---- src/protocols/rdp/audio_input.h | 52 ++++++++++++++++--------- src/protocols/rdp/guac_ai/ai_messages.c | 22 +++++++---- 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 9e88fb5c..0eb757d8 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -250,28 +250,49 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, guac_rdp_audio_buffer_ack(audio_buffer, "OK", GUAC_PROTOCOL_STATUS_SUCCESS); + guac_user_log(user, GUAC_LOG_DEBUG, "User is requesting to provide audio " + "input as %i-channel, %i Hz PCM audio at %i bytes/sample.", + audio_buffer->in_format.channels, + audio_buffer->in_format.rate, + audio_buffer->in_format.bps); + + pthread_mutex_unlock(&(audio_buffer->lock)); + +} + +void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer, + int rate, int channels, int bps) { + + pthread_mutex_lock(&(audio_buffer->lock)); + + /* Set output format */ + audio_buffer->out_format.rate = rate; + audio_buffer->out_format.channels = channels; + audio_buffer->out_format.bps = bps; + pthread_mutex_unlock(&(audio_buffer->lock)); } void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, - int rate, int channels, int bps, int packet_size, - guac_rdp_audio_buffer_flush_handler* flush_handler, void* data) { + int packet_frames, guac_rdp_audio_buffer_flush_handler* flush_handler, + void* data) { pthread_mutex_lock(&(audio_buffer->lock)); /* Reset buffer state to provided values */ audio_buffer->bytes_written = 0; - audio_buffer->out_format.rate = rate; - audio_buffer->out_format.channels = channels; - audio_buffer->out_format.bps = bps; - audio_buffer->packet_size = packet_size; audio_buffer->flush_handler = flush_handler; audio_buffer->data = data; + /* Calculate size of each packet in bytes */ + audio_buffer->packet_size = packet_frames + * audio_buffer->out_format.channels + * audio_buffer->out_format.bps; + /* Allocate new buffer */ free(audio_buffer->packet); - audio_buffer->packet = malloc(packet_size); + audio_buffer->packet = malloc(audio_buffer->packet_size); /* Acknowledge stream creation (if stream is ready to receive) */ guac_rdp_audio_buffer_ack(audio_buffer, diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index 0970671b..87eee9a9 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -180,29 +180,43 @@ guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(); void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, guac_user* user, guac_stream* stream, int rate, int channels, int bps); +/** + * Defines the output format that should be used by the audio buffer when + * flushing packets of audio data received via guac_rdp_audio_buffer_write(). + * As this format determines how the underlying packet buffer will be + * allocated, this function MUST be called prior to the call to + * guac_rdp_audio_buffer_begin(). + * + * @param audio_buffer + * The audio buffer to set the output format of. + * + * @param rate + * The rate of the audio stream expected by RDP, in samples per second. + * + * @param channels + * The number of channels included in the audio stream expected by RDP. + * + * @param bps + * The size of each sample within the audio stream expected by RDP, in + * bytes. + */ +void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer, + int rate, int channels, int bps); + /** * Begins handling of audio data received via guac_rdp_audio_buffer_write() and - * allocates the necessary underlying packet buffer. Audio packets of exactly - * packet_size bytes will be flushed as available using the provided - * flush_handler. + * allocates the necessary underlying packet buffer. Audio packets having + * exactly packet_frames frames will be flushed as available using the provided + * flush_handler. An audio frame is a set of single samples, one sample per + * channel. The guac_rdp_audio_buffer_set_output() function MUST have + * been invoked first. * * @param audio_buffer * The audio buffer to begin. * - * @param rate - * The rate of the audio stream expected by RDP, if any, in samples per - * second. - * - * @param channels - * The number of channels included in the audio stream expected by RDP, if - * any. - * - * @param bps - * The size of each sample within the audio stream expected by RDP, if any, - * in bytes. - * - * @param packet_size - * The number of bytes to include in all audio packets provided to the + * @param packet_frames + * The exact number of frames (a set of samples, one for each channel) + * which MUST be included in all audio packets provided to the * given flush_handler. * * @param flush_handler @@ -213,8 +227,8 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, * needs to be flushed. */ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, - int rate, int channels, int bps, int packet_size, - guac_rdp_audio_buffer_flush_handler* flush_handler, void* data); + int packet_frames, guac_rdp_audio_buffer_flush_handler* flush_handler, + void* data); /** * Writes the given buffer of audio data to the given audio buffer. A new diff --git a/src/protocols/rdp/guac_ai/ai_messages.c b/src/protocols/rdp/guac_ai/ai_messages.c index eeb3e287..c3465878 100644 --- a/src/protocols/rdp/guac_ai/ai_messages.c +++ b/src/protocols/rdp/guac_ai/ai_messages.c @@ -251,6 +251,9 @@ void guac_rdp_ai_process_version(guac_client* client, void guac_rdp_ai_process_formats(guac_client* client, IWTSVirtualChannel* channel, wStream* stream) { + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input; + UINT32 num_formats; Stream_Read_UINT32(stream, num_formats); /* NumFormats */ Stream_Seek_UINT32(stream); /* cbSizeFormatsPacket (MUST BE IGNORED) */ @@ -265,6 +268,10 @@ void guac_rdp_ai_process_formats(guac_client* client, if (format.tag != GUAC_RDP_WAVE_FORMAT_PCM) continue; + /* Set output format of internal audio buffer to match RDP server */ + guac_rdp_audio_buffer_set_output(audio_buffer, format.rate, + format.channels, format.bps / 8); + /* Accept single format */ guac_rdp_ai_send_incoming_data(channel); guac_rdp_ai_send_formats(channel, &format, 1); @@ -301,18 +308,19 @@ void guac_rdp_ai_process_open(guac_client* client, Stream_Read_UINT32(stream, packet_frames); /* FramesPerPacket */ Stream_Read_UINT32(stream, initial_format); /* InitialFormat */ - /* STUB */ - guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT: open: " - "packet_frames=%i, initial_format=%i", - packet_frames, initial_format); + guac_client_log(client, GUAC_LOG_DEBUG, "RDP server is accepting audio " + "input as %i-channel, %i Hz PCM audio at %i bytes/sample.", + audio_buffer->out_format.channels, + audio_buffer->out_format.rate, + audio_buffer->out_format.bps); /* Success */ guac_rdp_ai_send_formatchange(channel, initial_format); guac_rdp_ai_send_open_reply(channel, 0); - /* FIXME: Assuming mimetype of 16-bit 44100 Hz stereo PCM */ - guac_rdp_audio_buffer_begin(audio_buffer, packet_frames * 2 * 2, - 44100, 2, 2, guac_rdp_ai_flush_packet, channel); + /* Begin receiving audio data */ + guac_rdp_audio_buffer_begin(audio_buffer, packet_frames, + guac_rdp_ai_flush_packet, channel); } From 0be04ea54d0ce8dea9c57d29da15249b7c2db1f3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 1 Jun 2016 15:25:42 -0700 Subject: [PATCH 23/25] GUACAMOLE-25: Resample received audio as necessary. --- src/protocols/rdp/audio_input.c | 131 +++++++++++++++++++++++++++----- src/protocols/rdp/audio_input.h | 12 +++ 2 files changed, 126 insertions(+), 17 deletions(-) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 0eb757d8..eefb231b 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -246,6 +247,9 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, audio_buffer->in_format.channels = channels; audio_buffer->in_format.bps = bps; + /* Reset input counter */ + audio_buffer->total_bytes_received = 0; + /* Acknowledge stream creation (if buffer is ready to receive) */ guac_rdp_audio_buffer_ack(audio_buffer, "OK", GUAC_PROTOCOL_STATUS_SUCCESS); @@ -270,6 +274,9 @@ void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer, audio_buffer->out_format.channels = channels; audio_buffer->out_format.bps = bps; + /* Reset output counter */ + audio_buffer->total_bytes_sent = 0; + pthread_mutex_unlock(&(audio_buffer->lock)); } @@ -302,12 +309,99 @@ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, } +/** + * Reads a single sample from the given buffer of data, using the input + * format defined within the given audio buffer. Each read sample is + * translated to a signed 16-bit value, even if the input format is 8-bit. + * The offset into the given buffer will be determined according to the + * input and output formats, the number of bytes sent thus far, and the + * number of bytes received (excluding the contents of the buffer). + * + * @param audio_buffer + * The audio buffer dictating the format of the given data buffer, as + * well as the offset from which the sample should be read. + * + * @param buffer + * The buffer of raw PCM audio data from which the sample should be read. + * This buffer MUST NOT contain data already taken into account by the + * audio buffer's total_bytes_received counter. + * + * @param length + * The number of bytes within the given buffer of PCM data. + * + * @param sample + * A pointer to the int16_t in which the read sample should be stored. If + * the input format is 8-bit, the sample will be shifted left by 8 bits + * to produce a 16-bit sample. + * + * @return + * Non-zero if a sample was successfully read, zero if no data remains + * within the given buffer that has not already been mapped to an + * output sample. + */ +static int guac_rdp_audio_buffer_read_sample( + guac_rdp_audio_buffer* audio_buffer, const char* buffer, int length, + int16_t* sample) { + + int in_bps = audio_buffer->in_format.bps; + int in_rate = audio_buffer->in_format.rate; + int in_channels = audio_buffer->in_format.channels; + + int out_bps = audio_buffer->out_format.bps; + int out_rate = audio_buffer->out_format.rate; + int out_channels = audio_buffer->out_format.channels; + + /* Calculate position within audio output */ + int current_sample = audio_buffer->total_bytes_sent / out_bps; + int current_frame = current_sample / out_channels; + int current_channel = current_sample % out_channels; + + /* Map output channel to input channel */ + if (current_channel >= in_channels) + current_channel = in_channels - 1; + + /* Transform output position to input position */ + current_frame = (int) current_frame * ((double) in_rate / out_rate); + current_sample = current_frame * in_channels + current_channel; + + /* Calculate offset within given buffer from absolute input position */ + int offset = current_sample * in_bps + - audio_buffer->total_bytes_received; + + /* It should be impossible for the offset to ever go negative */ + assert(offset >= 0); + + /* Apply offset to buffer */ + buffer += offset; + length -= offset; + + /* Read only if sufficient data is present in the given buffer */ + if (length < in_bps) + return 0; + + /* Simply read sample directly if input is 16-bit */ + if (in_bps == 2) { + *sample = *((int16_t*) buffer); + return 1; + } + + /* Translate to 16-bit if input is 8-bit */ + if (in_bps == 1) { + *sample = *buffer << 8; + return 1; + } + + /* Accepted audio formats are required to be 8- or 16-bit */ + return 0; + +} + void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, char* buffer, int length) { - pthread_mutex_lock(&(audio_buffer->lock)); + int16_t sample; - /* FIXME: Assuming mimetype of "audio/L16;rate=44100,channels=2" */ + pthread_mutex_lock(&(audio_buffer->lock)); /* Ignore packet if there is no buffer */ if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) { @@ -315,27 +409,27 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, return; } + int out_bps = audio_buffer->out_format.bps; + /* Continuously write packets until no data remains */ - while (length > 0) { + while (guac_rdp_audio_buffer_read_sample(audio_buffer, + buffer, length, &sample) > 0) { - /* Calculate ideal size of chunk based on available space */ - int chunk_size = audio_buffer->packet_size - - audio_buffer->bytes_written; + char* current = audio_buffer->packet + audio_buffer->bytes_written; - /* Shrink chunk size if insufficient bytes are provided */ - if (length < chunk_size) - chunk_size = length; + /* Store as 16-bit or 8-bit, depending on output format */ + if (out_bps == 2) + *((int16_t*) current) = sample; + else if (out_bps == 1) + *current = sample >> 8; - /* Append buffer */ - memcpy(audio_buffer->packet + audio_buffer->bytes_written, - buffer, chunk_size); + /* Accepted audio formats are required to be 8- or 16-bit */ + else + assert(0); /* Update byte counters */ - length -= chunk_size; - audio_buffer->bytes_written += chunk_size; - - /* Advance to next chunk */ - buffer += chunk_size; + audio_buffer->bytes_written += out_bps; + audio_buffer->total_bytes_sent += out_bps; /* Invoke flush handler if full */ if (audio_buffer->bytes_written == audio_buffer->packet_size) { @@ -352,6 +446,9 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, } /* end packet write loop */ + /* Track current position in audio stream */ + audio_buffer->total_bytes_received += length; + pthread_mutex_unlock(&(audio_buffer->lock)); } diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index 87eee9a9..bd84fe10 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -121,6 +121,18 @@ typedef struct guac_rdp_audio_buffer { */ int bytes_written; + /** + * The total number of bytes having ever been received by the Guacamole + * server for the current audio stream. + */ + int total_bytes_received; + + /** + * The total number of bytes having ever been sent to the RDP server for + * the current audio stream. + */ + int total_bytes_sent; + /** * All audio data being prepared for sending to the AUDIO_INPUT channel. */ From 0d29694afc2c7eb1fa8a8fea011bd5a7ec9a99ac Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 1 Jun 2016 16:20:38 -0700 Subject: [PATCH 24/25] GUACAMOLE-25: Reset I/O counters upon end-of-stream. --- src/protocols/rdp/audio_input.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index eefb231b..dc078dc0 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -247,9 +247,6 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, audio_buffer->in_format.channels = channels; audio_buffer->in_format.bps = bps; - /* Reset input counter */ - audio_buffer->total_bytes_received = 0; - /* Acknowledge stream creation (if buffer is ready to receive) */ guac_rdp_audio_buffer_ack(audio_buffer, "OK", GUAC_PROTOCOL_STATUS_SUCCESS); @@ -274,9 +271,6 @@ void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer, audio_buffer->out_format.channels = channels; audio_buffer->out_format.bps = bps; - /* Reset output counter */ - audio_buffer->total_bytes_sent = 0; - pthread_mutex_unlock(&(audio_buffer->lock)); } @@ -470,6 +464,10 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) { audio_buffer->packet_size = 0; audio_buffer->flush_handler = NULL; + /* Reset I/O counters */ + audio_buffer->total_bytes_sent = 0; + audio_buffer->total_bytes_received = 0; + /* Free packet (if any) */ free(audio_buffer->packet); audio_buffer->packet = NULL; From 402b0393844fb2d6180ccaed341108b349404c83 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 1 Jun 2016 22:06:17 -0700 Subject: [PATCH 25/25] GUACAMOLE-25: Update documentation regarding loading of dynamic virtual channel plugins. --- src/protocols/rdp/audio_input.h | 12 +++++++++--- src/protocols/rdp/rdp_disp.h | 20 ++++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index bd84fe10..62806628 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -294,12 +294,18 @@ guac_user_blob_handler guac_rdp_audio_blob_handler; guac_user_end_handler guac_rdp_audio_end_handler; /** - * Loads Guacamole's "guacai" plugin for FreeRDP, adding support for the - * "AUDIO_INPUT" dynamic virtual channel. This function must ONLY be called - * after FreeRDP's "drdynvc" virtual channel plugin has been loaded. + * Adds Guacamole's "guacai" plugin to the list of dynamic virtual channel + * plugins to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only + * be loaded once guac_rdp_load_drdynvc() is invoked with the guac_rdp_dvc_list + * passed to this function. The "guacai" plugin ultimately adds support for the + * "AUDIO_INPUT" dynamic virtual channel. * * @param context * The rdpContext associated with the active RDP session. + * + * @param list + * The guac_rdp_dvc_list to which the "guacai" plugin should be added, such + * that it may later be loaded by guac_rdp_load_drdynvc(). */ void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list); diff --git a/src/protocols/rdp/rdp_disp.h b/src/protocols/rdp/rdp_disp.h index 093e7ed4..0f34fe12 100644 --- a/src/protocols/rdp/rdp_disp.h +++ b/src/protocols/rdp/rdp_disp.h @@ -97,12 +97,24 @@ guac_rdp_disp* guac_rdp_disp_alloc(); void guac_rdp_disp_free(guac_rdp_disp* disp); /** - * Loads the "disp" plugin for FreeRDP. It is still up to external code to - * detect when the "disp" channel is connected, and update the guac_rdp_disp - * with a call to guac_rdp_disp_connect(). - * * @param context The rdpContext associated with the active RDP session. */ +/** + * Adds FreeRDP's "disp" plugin to the list of dynamic virtual channel plugins + * to be loaded by FreeRDP's "drdynvc" plugin. The plugin will only be loaded + * once guac_rdp_load_drdynvc() is invoked with the guac_rdp_dvc_list passed to + * this function. The "disp" plugin ultimately adds support for the Display + * Update channel. NOTE: It is still up to external code to detect when the + * "disp" channel is connected, and update the guac_rdp_disp with a call to + * guac_rdp_disp_connect(). + * + * @param context + * The rdpContext associated with the active RDP session. + * + * @param list + * The guac_rdp_dvc_list to which the "disp" plugin should be added, such + * that it may later be loaded by guac_rdp_load_drdynvc(). + */ void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list); #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT