guacai-messages.c (13828B)
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-messages.h" 22#include "rdp.h" 23 24#include <freerdp/dvc.h> 25#include <guacamole/client.h> 26#include <winpr/stream.h> 27 28#include <stdlib.h> 29 30/** 31 * Reads AUDIO_FORMAT data from the given stream into the given struct. 32 * 33 * @param stream 34 * The stream to read AUDIO_FORMAT data from. 35 * 36 * @param format 37 * The structure to populate with data from the stream. 38 * 39 * @return 40 * Zero on success or non-zero if an error occurs processing the format. 41 */ 42static int guac_rdp_ai_read_format(wStream* stream, 43 guac_rdp_ai_format* format) { 44 45 /* Check that we have at least 18 bytes (5 x UINT16, 2 x UINT32) */ 46 if (Stream_GetRemainingLength(stream) < 18) 47 return 1; 48 49 /* Read audio format into structure */ 50 Stream_Read_UINT16(stream, format->tag); /* wFormatTag */ 51 Stream_Read_UINT16(stream, format->channels); /* nChannels */ 52 Stream_Read_UINT32(stream, format->rate); /* nSamplesPerSec */ 53 Stream_Read_UINT32(stream, format->bytes_per_sec); /* nAvgBytesPerSec */ 54 Stream_Read_UINT16(stream, format->block_align); /* nBlockAlign */ 55 Stream_Read_UINT16(stream, format->bps); /* wBitsPerSample */ 56 Stream_Read_UINT16(stream, format->data_size); /* cbSize */ 57 58 /* Read arbitrary data block (if applicable) and data is available. */ 59 if (format->data_size != 0) { 60 61 /* Check to make sure Stream contains expected bytes. */ 62 if (Stream_GetRemainingLength(stream) < format->data_size) 63 return 1; 64 65 format->data = Stream_Pointer(stream); /* data */ 66 Stream_Seek(stream, format->data_size); 67 68 } 69 70 return 0; 71 72} 73 74/** 75 * Writes AUDIO_FORMAT data to the given stream from the given struct. 76 * 77 * @param stream 78 * The stream to write AUDIO_FORMAT data to. 79 * 80 * @param format 81 * The structure containing the data that should be written to the stream. 82 */ 83static void guac_rdp_ai_write_format(wStream* stream, 84 guac_rdp_ai_format* format) { 85 86 /* Write audio format into structure */ 87 Stream_Write_UINT16(stream, format->tag); /* wFormatTag */ 88 Stream_Write_UINT16(stream, format->channels); /* nChannels */ 89 Stream_Write_UINT32(stream, format->rate); /* nSamplesPerSec */ 90 Stream_Write_UINT32(stream, format->bytes_per_sec); /* nAvgBytesPerSec */ 91 Stream_Write_UINT16(stream, format->block_align); /* nBlockAlign */ 92 Stream_Write_UINT16(stream, format->bps); /* wBitsPerSample */ 93 Stream_Write_UINT16(stream, format->data_size); /* cbSize */ 94 95 /* Write arbitrary data block (if applicable) */ 96 if (format->data_size != 0) 97 Stream_Write(stream, format->data, format->data_size); 98 99} 100 101/** 102 * Sends a Data Incoming PDU along the given channel. A Data Incoming PDU is 103 * used by the client to indicate to the server that format or audio data is 104 * about to be sent. 105 * 106 * @param channel 107 * The channel along which the PDU should be sent. 108 */ 109static void guac_rdp_ai_send_incoming_data(IWTSVirtualChannel* channel) { 110 111 /* Build data incoming PDU */ 112 wStream* stream = Stream_New(NULL, 1); 113 Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_DATA_INCOMING); /* MessageId */ 114 115 /* Send stream */ 116 channel->Write(channel, (UINT32) Stream_GetPosition(stream), 117 Stream_Buffer(stream), NULL); 118 Stream_Free(stream, TRUE); 119 120} 121 122/** 123 * Sends a Data PDU along the given channel. A Data PDU is used by the client 124 * to send actual audio data following a Data Incoming PDU. 125 * 126 * @param channel 127 * The channel along which the PDU should be sent. 128 * 129 * @param buffer 130 * The audio data to send. 131 * 132 * @param length 133 * The number of bytes of audio data to send. 134 */ 135static void guac_rdp_ai_send_data(IWTSVirtualChannel* channel, 136 char* buffer, int length) { 137 138 /* Build data PDU */ 139 wStream* stream = Stream_New(NULL, length + 1); 140 Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_DATA); /* MessageId */ 141 Stream_Write(stream, buffer, length); /* Data */ 142 143 /* Send stream */ 144 channel->Write(channel, (UINT32) Stream_GetPosition(stream), 145 Stream_Buffer(stream), NULL); 146 Stream_Free(stream, TRUE); 147 148} 149 150/** 151 * Sends a Sound Formats PDU along the given channel. A Sound Formats PDU is 152 * used by the client to indicate to the server which formats of audio it 153 * supports (in response to the server sending exactly the same type of PDU). 154 * This PDU MUST be preceded by the Data Incoming PDU. 155 * 156 * @param channel 157 * The channel along which the PDU should be sent. 158 * 159 * @param formats 160 * An array of all supported formats. 161 * 162 * @param num_formats 163 * The number of entries in the formats array. 164 */ 165static void guac_rdp_ai_send_formats(IWTSVirtualChannel* channel, 166 guac_rdp_ai_format* formats, int num_formats) { 167 168 int index; 169 int packet_size = 9; 170 171 /* Calculate packet size */ 172 for (index = 0; index < num_formats; index++) 173 packet_size += 18 + formats[index].data_size; 174 175 wStream* stream = Stream_New(NULL, packet_size); 176 177 /* Write header */ 178 Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_FORMATS); /* MessageId */ 179 Stream_Write_UINT32(stream, num_formats); /* NumFormats */ 180 Stream_Write_UINT32(stream, packet_size); /* cbSizeFormatsPacket */ 181 182 /* Write all formats */ 183 for (index = 0; index < num_formats; index++) 184 guac_rdp_ai_write_format(stream, &(formats[index])); 185 186 /* Send PDU */ 187 channel->Write(channel, (UINT32) Stream_GetPosition(stream), 188 Stream_Buffer(stream), NULL); 189 Stream_Free(stream, TRUE); 190 191} 192 193/** 194 * Sends an Open Reply PDU along the given channel. An Open Reply PDU is 195 * used by the client to acknowledge the successful opening of the AUDIO_INPUT 196 * channel. 197 * 198 * @param channel 199 * The channel along which the PDU should be sent. 200 * 201 * @param result 202 * The HRESULT code to send to the server indicating success, failure, etc. 203 */ 204static void guac_rdp_ai_send_open_reply(IWTSVirtualChannel* channel, 205 UINT32 result) { 206 207 /* Build open reply PDU */ 208 wStream* stream = Stream_New(NULL, 5); 209 Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_OPEN_REPLY); /* MessageId */ 210 Stream_Write_UINT32(stream, result); /* Result */ 211 212 /* Send stream */ 213 channel->Write(channel, (UINT32) Stream_GetPosition(stream), 214 Stream_Buffer(stream), NULL); 215 Stream_Free(stream, TRUE); 216 217} 218 219/** 220 * Sends a Format Change PDU along the given channel. A Format Change PDU is 221 * used by the client to acknowledge the format being used for data sent 222 * along the AUDIO_INPUT channel. 223 * 224 * @param channel 225 * The channel along which the PDU should be sent. 226 * 227 * @param format 228 * The index of the format being acknowledged, which must be the index of 229 * the format within the original Sound Formats PDU received from the 230 * server. 231 */ 232static void guac_rdp_ai_send_formatchange(IWTSVirtualChannel* channel, 233 UINT32 format) { 234 235 /* Build format change PDU */ 236 wStream* stream = Stream_New(NULL, 5); 237 Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_FORMATCHANGE); /* MessageId */ 238 Stream_Write_UINT32(stream, format); /* NewFormat */ 239 240 /* Send stream */ 241 channel->Write(channel, (UINT32) Stream_GetPosition(stream), 242 Stream_Buffer(stream), NULL); 243 Stream_Free(stream, TRUE); 244 245} 246 247void guac_rdp_ai_process_version(guac_client* client, 248 IWTSVirtualChannel* channel, wStream* stream) { 249 250 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 251 252 /* Verify we have at least 4 bytes available (UINT32) */ 253 if (Stream_GetRemainingLength(stream) < 4) { 254 guac_client_log(client, GUAC_LOG_WARNING, "Audio input Version PDU " 255 "does not contain the expected number of bytes. Audio input " 256 "redirection may not work as expected."); 257 return; 258 } 259 260 UINT32 version; 261 Stream_Read_UINT32(stream, version); 262 263 /* Warn if server's version number is incorrect */ 264 if (version != 1) 265 guac_client_log(client, GUAC_LOG_WARNING, 266 "Server reports AUDIO_INPUT version %i, not 1", version); 267 268 /* Build response version PDU */ 269 wStream* response = Stream_New(NULL, 5); 270 Stream_Write_UINT8(response, GUAC_RDP_MSG_SNDIN_VERSION); /* MessageId */ 271 Stream_Write_UINT32(response, 1); /* Version */ 272 273 /* Send response */ 274 pthread_mutex_lock(&(rdp_client->message_lock)); 275 channel->Write(channel, (UINT32) Stream_GetPosition(response), Stream_Buffer(response), NULL); 276 pthread_mutex_unlock(&(rdp_client->message_lock)); 277 Stream_Free(response, TRUE); 278 279} 280 281void guac_rdp_ai_process_formats(guac_client* client, 282 IWTSVirtualChannel* channel, wStream* stream) { 283 284 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 285 guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input; 286 287 /* Verify we have at least 8 bytes available (2 x UINT32) */ 288 if (Stream_GetRemainingLength(stream) < 8) { 289 guac_client_log(client, GUAC_LOG_WARNING, "Audio input Sound Formats " 290 "PDU does not contain the expected number of bytes. Audio " 291 "input redirection may not work as expected."); 292 return; 293 } 294 295 UINT32 num_formats; 296 Stream_Read_UINT32(stream, num_formats); /* NumFormats */ 297 Stream_Seek_UINT32(stream); /* cbSizeFormatsPacket (MUST BE IGNORED) */ 298 299 UINT32 index; 300 for (index = 0; index < num_formats; index++) { 301 302 guac_rdp_ai_format format; 303 if (guac_rdp_ai_read_format(stream, &format)) { 304 guac_client_log(client, GUAC_LOG_WARNING, "Error occurred " 305 "processing audio input formats. Audio input redirection " 306 "may not work as expected."); 307 return; 308 } 309 310 /* Ignore anything but WAVE_FORMAT_PCM */ 311 if (format.tag != GUAC_RDP_WAVE_FORMAT_PCM) 312 continue; 313 314 /* Set output format of internal audio buffer to match RDP server */ 315 guac_rdp_audio_buffer_set_output(audio_buffer, format.rate, 316 format.channels, format.bps / 8); 317 318 /* Accept single format */ 319 pthread_mutex_lock(&(rdp_client->message_lock)); 320 guac_rdp_ai_send_incoming_data(channel); 321 guac_rdp_ai_send_formats(channel, &format, 1); 322 pthread_mutex_unlock(&(rdp_client->message_lock)); 323 return; 324 325 } 326 327 /* No formats available */ 328 guac_client_log(client, GUAC_LOG_WARNING, "AUDIO_INPUT: No WAVE format."); 329 pthread_mutex_lock(&(rdp_client->message_lock)); 330 guac_rdp_ai_send_incoming_data(channel); 331 guac_rdp_ai_send_formats(channel, NULL, 0); 332 pthread_mutex_unlock(&(rdp_client->message_lock)); 333 334} 335 336void guac_rdp_ai_flush_packet(guac_rdp_audio_buffer* audio_buffer, int length) { 337 338 guac_client* client = audio_buffer->client; 339 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 340 IWTSVirtualChannel* channel = (IWTSVirtualChannel*) audio_buffer->data; 341 342 /* Send data over channel */ 343 pthread_mutex_lock(&(rdp_client->message_lock)); 344 guac_rdp_ai_send_incoming_data(channel); 345 guac_rdp_ai_send_data(channel, audio_buffer->packet, length); 346 pthread_mutex_unlock(&(rdp_client->message_lock)); 347 348} 349 350void guac_rdp_ai_process_open(guac_client* client, 351 IWTSVirtualChannel* channel, wStream* stream) { 352 353 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 354 guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input; 355 356 /* Verify we have at least 8 bytes available (2 x UINT32) */ 357 if (Stream_GetRemainingLength(stream) < 8) { 358 guac_client_log(client, GUAC_LOG_WARNING, "Audio input Open PDU does " 359 "not contain the expected number of bytes. Audio input " 360 "redirection may not work as expected."); 361 return; 362 } 363 364 UINT32 packet_frames; 365 UINT32 initial_format; 366 367 Stream_Read_UINT32(stream, packet_frames); /* FramesPerPacket */ 368 Stream_Read_UINT32(stream, initial_format); /* InitialFormat */ 369 370 guac_client_log(client, GUAC_LOG_DEBUG, "RDP server is accepting audio " 371 "input as %i-channel, %i Hz PCM audio at %i bytes/sample.", 372 audio_buffer->out_format.channels, 373 audio_buffer->out_format.rate, 374 audio_buffer->out_format.bps); 375 376 /* Success */ 377 pthread_mutex_lock(&(rdp_client->message_lock)); 378 guac_rdp_ai_send_formatchange(channel, initial_format); 379 guac_rdp_ai_send_open_reply(channel, 0); 380 pthread_mutex_unlock(&(rdp_client->message_lock)); 381 382 /* Begin receiving audio data */ 383 guac_rdp_audio_buffer_begin(audio_buffer, packet_frames, 384 guac_rdp_ai_flush_packet, channel); 385 386} 387 388void guac_rdp_ai_process_formatchange(guac_client* client, 389 IWTSVirtualChannel* channel, wStream* stream) { 390 391 /* Should not be called as we only accept one format */ 392 guac_client_log(client, GUAC_LOG_DEBUG, 393 "RDP server requesting AUDIO_INPUT format change despite only one " 394 "format available."); 395 396} 397