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