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