guacai.c (10320B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "channels/audio-input/audio-buffer.h" 21#include "plugins/guacai/guacai.h" 22#include "plugins/guacai/guacai-messages.h" 23#include "plugins/ptr-string.h" 24#include "rdp.h" 25 26#include <freerdp/dvc.h> 27#include <freerdp/settings.h> 28#include <guacamole/client.h> 29#include <guacamole/mem.h> 30#include <winpr/stream.h> 31#include <winpr/wtsapi.h> 32#include <winpr/wtypes.h> 33 34#include <stdlib.h> 35 36/** 37 * Handles the given data received along the AUDIO_INPUT channel of the RDP 38 * connection associated with the given guac_client. This handler is 39 * API-independent and is invoked by API-dependent guac_rdp_ai_data callback 40 * specific to the version of FreeRDP installed. 41 * 42 * @param client 43 * The guac_client associated with RDP connection having the AUDIO_INPUT 44 * connection along which the given data was received. 45 * 46 * @param channel 47 * A reference to the IWTSVirtualChannel instance along which responses 48 * should be sent. 49 * 50 * @param stream 51 * The data received along the AUDIO_INPUT channel. 52 */ 53static void guac_rdp_ai_handle_data(guac_client* client, 54 IWTSVirtualChannel* channel, wStream* stream) { 55 56 /* Verify we have at least 1 byte in the stream (UINT8) */ 57 if (Stream_GetRemainingLength(stream) < 1) { 58 guac_client_log(client, GUAC_LOG_WARNING, "Audio input PDU header does " 59 "not contain the expected number of bytes. Audio input " 60 "redirection may not work as expected."); 61 return; 62 } 63 64 /* Read message ID from received PDU */ 65 BYTE message_id; 66 Stream_Read_UINT8(stream, message_id); 67 68 /* Invoke appropriate message processor based on ID */ 69 switch (message_id) { 70 71 /* Version PDU */ 72 case GUAC_RDP_MSG_SNDIN_VERSION: 73 guac_rdp_ai_process_version(client, channel, stream); 74 break; 75 76 /* Sound Formats PDU */ 77 case GUAC_RDP_MSG_SNDIN_FORMATS: 78 guac_rdp_ai_process_formats(client, channel, stream); 79 break; 80 81 /* Open PDU */ 82 case GUAC_RDP_MSG_SNDIN_OPEN: 83 guac_rdp_ai_process_open(client, channel, stream); 84 break; 85 86 /* Format Change PDU */ 87 case GUAC_RDP_MSG_SNDIN_FORMATCHANGE: 88 guac_rdp_ai_process_formatchange(client, channel, stream); 89 break; 90 91 /* Log unknown message IDs */ 92 default: 93 guac_client_log(client, GUAC_LOG_DEBUG, 94 "Unknown AUDIO_INPUT message ID: 0x%x", message_id); 95 96 } 97 98} 99 100/** 101 * Callback which is invoked when data is received along a connection to the 102 * AUDIO_INPUT plugin. 103 * 104 * @param channel_callback 105 * The IWTSVirtualChannelCallback structure to which this callback was 106 * originally assigned. 107 * 108 * @param stream 109 * The data received. 110 * 111 * @return 112 * Always zero. 113 */ 114static UINT guac_rdp_ai_data(IWTSVirtualChannelCallback* channel_callback, 115 wStream* stream) { 116 117 guac_rdp_ai_channel_callback* ai_channel_callback = 118 (guac_rdp_ai_channel_callback*) channel_callback; 119 IWTSVirtualChannel* channel = ai_channel_callback->channel; 120 121 /* Invoke generalized (API-independent) data handler */ 122 guac_rdp_ai_handle_data(ai_channel_callback->client, channel, stream); 123 124 return CHANNEL_RC_OK; 125 126} 127 128/** 129 * Callback which is invoked when a connection to the AUDIO_INPUT plugin is 130 * closed. 131 * 132 * @param channel_callback 133 * The IWTSVirtualChannelCallback structure to which this callback was 134 * originally assigned. 135 * 136 * @return 137 * Always zero. 138 */ 139static UINT guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) { 140 141 guac_rdp_ai_channel_callback* ai_channel_callback = 142 (guac_rdp_ai_channel_callback*) channel_callback; 143 144 guac_client* client = ai_channel_callback->client; 145 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 146 guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input; 147 148 /* Log closure of AUDIO_INPUT channel */ 149 guac_client_log(client, GUAC_LOG_DEBUG, 150 "AUDIO_INPUT channel connection closed"); 151 152 guac_rdp_audio_buffer_end(audio_buffer); 153 guac_mem_free(ai_channel_callback); 154 return CHANNEL_RC_OK; 155 156} 157 158/** 159 * Callback which is invoked when a new connection is received by the 160 * AUDIO_INPUT plugin. Additional callbacks required to handle received data 161 * and closure of the connection must be installed at this point. 162 * 163 * @param listener_callback 164 * The IWTSListenerCallback structure associated with the AUDIO_INPUT 165 * plugin receiving the new connection. 166 * 167 * @param channel 168 * A reference to the IWTSVirtualChannel instance along which data related 169 * to the AUDIO_INPUT channel should be sent. 170 * 171 * @param data 172 * Absolutely no idea. According to Microsoft's documentation for the 173 * function prototype on which FreeRDP's API appears to be based: "This 174 * parameter is not implemented and is reserved for future use." 175 * 176 * @param accept 177 * Pointer to a flag which should be set to TRUE if the connection should 178 * be accepted or FALSE otherwise. In the case of FreeRDP, this value 179 * defaults to TRUE, and TRUE absolutely MUST be identically 1 or it will 180 * be interpreted as FALSE. 181 * 182 * @param channel_callback 183 * A pointer to the location that the new IWTSVirtualChannelCallback 184 * structure containing the required callbacks should be assigned. 185 * 186 * @return 187 * Always zero. 188 */ 189static UINT guac_rdp_ai_new_connection( 190 IWTSListenerCallback* listener_callback, IWTSVirtualChannel* channel, 191 BYTE* data, int* accept, 192 IWTSVirtualChannelCallback** channel_callback) { 193 194 guac_rdp_ai_listener_callback* ai_listener_callback = 195 (guac_rdp_ai_listener_callback*) listener_callback; 196 197 /* Log new AUDIO_INPUT connection */ 198 guac_client_log(ai_listener_callback->client, GUAC_LOG_DEBUG, 199 "New AUDIO_INPUT channel connection"); 200 201 /* Allocate new channel callback */ 202 guac_rdp_ai_channel_callback* ai_channel_callback = 203 guac_mem_zalloc(sizeof(guac_rdp_ai_channel_callback)); 204 205 /* Init listener callback with data from plugin */ 206 ai_channel_callback->client = ai_listener_callback->client; 207 ai_channel_callback->channel = channel; 208 ai_channel_callback->parent.OnDataReceived = guac_rdp_ai_data; 209 ai_channel_callback->parent.OnClose = guac_rdp_ai_close; 210 211 /* Return callback through pointer */ 212 *channel_callback = (IWTSVirtualChannelCallback*) ai_channel_callback; 213 214 return CHANNEL_RC_OK; 215 216} 217 218/** 219 * Callback which is invoked when the AUDIO_INPUT plugin has been loaded and 220 * needs to be initialized with other callbacks and data. 221 * 222 * @param plugin 223 * The AUDIO_INPUT plugin that needs to be initialied. 224 * 225 * @param manager 226 * The IWTSVirtualChannelManager instance with which the AUDIO_INPUT plugin 227 * must be registered. 228 * 229 * @return 230 * Always zero. 231 */ 232static UINT guac_rdp_ai_initialize(IWTSPlugin* plugin, 233 IWTSVirtualChannelManager* manager) { 234 235 /* Allocate new listener callback */ 236 guac_rdp_ai_listener_callback* ai_listener_callback = 237 guac_mem_zalloc(sizeof(guac_rdp_ai_listener_callback)); 238 239 /* Ensure listener callback is freed when plugin is terminated */ 240 guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin; 241 ai_plugin->listener_callback = ai_listener_callback; 242 243 /* Init listener callback with data from plugin */ 244 ai_listener_callback->client = ai_plugin->client; 245 ai_listener_callback->parent.OnNewChannelConnection = 246 guac_rdp_ai_new_connection; 247 248 /* Register listener for "AUDIO_INPUT" channel */ 249 manager->CreateListener(manager, "AUDIO_INPUT", 0, 250 (IWTSListenerCallback*) ai_listener_callback, NULL); 251 252 return CHANNEL_RC_OK; 253 254} 255 256/** 257 * Callback which is invoked when all connections to the AUDIO_INPUT plugin 258 * have closed and the plugin is being unloaded. 259 * 260 * @param plugin 261 * The AUDIO_INPUT plugin being unloaded. 262 * 263 * @return 264 * Always zero. 265 */ 266static UINT guac_rdp_ai_terminated(IWTSPlugin* plugin) { 267 268 guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin; 269 guac_client* client = ai_plugin->client; 270 271 /* Free all non-FreeRDP data */ 272 guac_mem_free(ai_plugin->listener_callback); 273 guac_mem_free(ai_plugin); 274 275 guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin unloaded."); 276 return CHANNEL_RC_OK; 277 278} 279 280/** 281 * Entry point for AUDIO_INPUT dynamic virtual channel. 282 */ 283UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { 284 285 /* Pull guac_client from arguments */ 286 ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); 287 guac_client* client = (guac_client*) guac_rdp_string_to_ptr(args->argv[1]); 288 289 /* Pull previously-allocated plugin */ 290 guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) 291 pEntryPoints->GetPlugin(pEntryPoints, "guacai"); 292 293 /* If no such plugin allocated, allocate and register it now */ 294 if (ai_plugin == NULL) { 295 296 /* Init plugin callbacks and data */ 297 ai_plugin = guac_mem_zalloc(sizeof(guac_rdp_ai_plugin)); 298 ai_plugin->parent.Initialize = guac_rdp_ai_initialize; 299 ai_plugin->parent.Terminated = guac_rdp_ai_terminated; 300 ai_plugin->client = client; 301 302 /* Register plugin as "guacai" for later retrieval */ 303 pEntryPoints->RegisterPlugin(pEntryPoints, "guacai", 304 (IWTSPlugin*) ai_plugin); 305 306 guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin loaded."); 307 } 308 309 return CHANNEL_RC_OK; 310 311} 312