/* * 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 "ai_service.h" #include "audio_input.h" #include "ptr_string.h" #include "rdp.h" #include #include #include #include #include #include #include /** * 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 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, IWTSVirtualChannel* channel, wStream* stream) { /* Read message ID from received PDU */ BYTE message_id; Stream_Read_UINT8(stream, 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); } } /** * Callback which is invoked when data is received along a connection to the * AUDIO_INPUT plugin. * * @param channel_callback * The IWTSVirtualChannelCallback structure to which this callback was * originally assigned. * * @param stream * The data received. * * @return * Always zero. */ static UINT guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, wStream* stream) { 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, channel, stream); 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 UINT 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(client, GUAC_LOG_DEBUG, "AUDIO_INPUT channel connection closed"); guac_rdp_audio_buffer_end(audio_buffer); 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 UINT 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->channel = channel; 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 UINT 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 UINT 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. */ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { /* Pull guac_client from arguments */ ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]); /* 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; }