cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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