Milestone 5: deliver embedded RDP sessions and lifecycle hardening
This commit is contained in:
23
third_party/FreeRDP/channels/audin/server/CMakeLists.txt
vendored
Normal file
23
third_party/FreeRDP/channels/audin/server/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
define_channel_server("audin")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS audin.c)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS freerdp)
|
||||
add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
|
||||
927
third_party/FreeRDP/channels/audin/server/audin.c
vendored
Normal file
927
third_party/FreeRDP/channels/audin/server/audin.c
vendored
Normal file
@@ -0,0 +1,927 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Server Audio Input Virtual Channel
|
||||
*
|
||||
* Copyright 2012 Vic Lee
|
||||
* Copyright 2015 Thincast Technologies GmbH
|
||||
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
|
||||
* Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
|
||||
*
|
||||
* Licensed 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 <freerdp/config.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/assert.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/thread.h>
|
||||
#include <winpr/stream.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/server/server-common.h>
|
||||
#include <freerdp/server/audin.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#define AUDIN_TAG CHANNELS_TAG("audin.server")
|
||||
|
||||
#define SNDIN_HEADER_SIZE 1
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MSG_SNDIN_VERSION = 0x01,
|
||||
MSG_SNDIN_FORMATS = 0x02,
|
||||
MSG_SNDIN_OPEN = 0x03,
|
||||
MSG_SNDIN_OPEN_REPLY = 0x04,
|
||||
MSG_SNDIN_DATA_INCOMING = 0x05,
|
||||
MSG_SNDIN_DATA = 0x06,
|
||||
MSG_SNDIN_FORMATCHANGE = 0x07,
|
||||
} MSG_SNDIN;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
audin_server_context context;
|
||||
|
||||
HANDLE stopEvent;
|
||||
|
||||
HANDLE thread;
|
||||
void* audin_channel;
|
||||
|
||||
DWORD SessionId;
|
||||
|
||||
AUDIO_FORMAT* audin_server_formats;
|
||||
UINT32 audin_n_server_formats;
|
||||
AUDIO_FORMAT* audin_negotiated_format;
|
||||
UINT32 audin_client_format_idx;
|
||||
wLog* log;
|
||||
} audin_server;
|
||||
|
||||
static UINT audin_server_recv_version(audin_server_context* context, wStream* s,
|
||||
const SNDIN_PDU* header)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
SNDIN_VERSION pdu = WINPR_C_ARRAY_INIT;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(header);
|
||||
|
||||
pdu.Header = *header;
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
|
||||
return ERROR_NO_DATA;
|
||||
|
||||
{
|
||||
const UINT32 version = Stream_Get_UINT32(s);
|
||||
switch (version)
|
||||
{
|
||||
case SNDIN_VERSION_Version_1:
|
||||
pdu.Version = SNDIN_VERSION_Version_1;
|
||||
break;
|
||||
case SNDIN_VERSION_Version_2:
|
||||
pdu.Version = SNDIN_VERSION_Version_2;
|
||||
break;
|
||||
default:
|
||||
pdu.Version = SNDIN_VERSION_Version_2;
|
||||
WLog_Print(audin->log, WLOG_WARN,
|
||||
"Received unsupported channel version %" PRIu32
|
||||
", using highest supported version %u",
|
||||
version, pdu.Version);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IFCALLRET(context->ReceiveVersion, error, context, &pdu);
|
||||
if (error)
|
||||
WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveVersion failed with error %" PRIu32 "",
|
||||
error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT audin_server_recv_formats(audin_server_context* context, wStream* s,
|
||||
const SNDIN_PDU* header)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
SNDIN_FORMATS pdu = WINPR_C_ARRAY_INIT;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(header);
|
||||
|
||||
pdu.Header = *header;
|
||||
|
||||
/* Implementations MUST, at a minimum, support WAVE_FORMAT_PCM (0x0001) */
|
||||
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4 + 4 + 18))
|
||||
return ERROR_NO_DATA;
|
||||
|
||||
Stream_Read_UINT32(s, pdu.NumFormats);
|
||||
Stream_Read_UINT32(s, pdu.cbSizeFormatsPacket);
|
||||
|
||||
if (pdu.NumFormats == 0)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "Sound Formats PDU contains no formats");
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
pdu.SoundFormats = audio_formats_new(pdu.NumFormats);
|
||||
if (!pdu.SoundFormats)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "Failed to allocate %u SoundFormats", pdu.NumFormats);
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
for (UINT32 i = 0; i < pdu.NumFormats; ++i)
|
||||
{
|
||||
AUDIO_FORMAT* format = &pdu.SoundFormats[i];
|
||||
|
||||
if (!audio_format_read(s, format))
|
||||
goto fail;
|
||||
|
||||
audio_format_print(audin->log, WLOG_DEBUG, format);
|
||||
}
|
||||
|
||||
if (pdu.cbSizeFormatsPacket != Stream_GetPosition(s))
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_WARN,
|
||||
"cbSizeFormatsPacket is invalid! Expected: %u Got: %zu. Fixing size",
|
||||
pdu.cbSizeFormatsPacket, Stream_GetPosition(s));
|
||||
const size_t pos = Stream_GetPosition(s);
|
||||
if (pos > UINT32_MAX)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "Stream too long, %" PRIuz " exceeds UINT32_MAX",
|
||||
pos);
|
||||
error = ERROR_INVALID_PARAMETER;
|
||||
goto fail;
|
||||
}
|
||||
pdu.cbSizeFormatsPacket = (UINT32)pos;
|
||||
}
|
||||
|
||||
pdu.ExtraDataSize = Stream_GetRemainingLength(s);
|
||||
|
||||
IFCALLRET(context->ReceiveFormats, error, context, &pdu);
|
||||
if (error)
|
||||
WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveFormats failed with error %" PRIu32 "",
|
||||
error);
|
||||
|
||||
fail:
|
||||
audio_formats_free(pdu.SoundFormats, pdu.NumFormats);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT audin_server_recv_open_reply(audin_server_context* context, wStream* s,
|
||||
const SNDIN_PDU* header)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
SNDIN_OPEN_REPLY pdu = WINPR_C_ARRAY_INIT;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(header);
|
||||
|
||||
pdu.Header = *header;
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
|
||||
return ERROR_NO_DATA;
|
||||
|
||||
Stream_Read_UINT32(s, pdu.Result);
|
||||
|
||||
IFCALLRET(context->OpenReply, error, context, &pdu);
|
||||
if (error)
|
||||
WLog_Print(audin->log, WLOG_ERROR, "context->OpenReply failed with error %" PRIu32 "",
|
||||
error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT audin_server_recv_data_incoming(audin_server_context* context,
|
||||
WINPR_ATTR_UNUSED wStream* s, const SNDIN_PDU* header)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
SNDIN_DATA_INCOMING pdu = WINPR_C_ARRAY_INIT;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(header);
|
||||
|
||||
pdu.Header = *header;
|
||||
|
||||
IFCALLRET(context->IncomingData, error, context, &pdu);
|
||||
if (error)
|
||||
WLog_Print(audin->log, WLOG_ERROR, "context->IncomingData failed with error %" PRIu32 "",
|
||||
error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT audin_server_recv_data(audin_server_context* context, wStream* s,
|
||||
const SNDIN_PDU* header)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
SNDIN_DATA pdu = WINPR_C_ARRAY_INIT;
|
||||
wStream dataBuffer = WINPR_C_ARRAY_INIT;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(header);
|
||||
|
||||
pdu.Header = *header;
|
||||
|
||||
pdu.Data = Stream_StaticInit(&dataBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s));
|
||||
|
||||
IFCALLRET(context->Data, error, context, &pdu);
|
||||
if (error)
|
||||
WLog_Print(audin->log, WLOG_ERROR, "context->Data failed with error %" PRIu32 "", error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT audin_server_recv_format_change(audin_server_context* context, wStream* s,
|
||||
const SNDIN_PDU* header)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
SNDIN_FORMATCHANGE pdu = WINPR_C_ARRAY_INIT;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(header);
|
||||
|
||||
pdu.Header = *header;
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4))
|
||||
return ERROR_NO_DATA;
|
||||
|
||||
Stream_Read_UINT32(s, pdu.NewFormat);
|
||||
|
||||
IFCALLRET(context->ReceiveFormatChange, error, context, &pdu);
|
||||
if (error)
|
||||
WLog_Print(audin->log, WLOG_ERROR,
|
||||
"context->ReceiveFormatChange failed with error %" PRIu32 "", error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static DWORD WINAPI audin_server_thread_func(LPVOID arg)
|
||||
{
|
||||
wStream* s = nullptr;
|
||||
void* buffer = nullptr;
|
||||
DWORD nCount = 0;
|
||||
HANDLE events[8] = WINPR_C_ARRAY_INIT;
|
||||
BOOL ready = FALSE;
|
||||
HANDLE ChannelEvent = nullptr;
|
||||
DWORD BytesReturned = 0;
|
||||
audin_server* audin = (audin_server*)arg;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
DWORD status = ERROR_INTERNAL_ERROR;
|
||||
|
||||
WINPR_ASSERT(audin);
|
||||
|
||||
if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualEventHandle, &buffer,
|
||||
&BytesReturned) == TRUE)
|
||||
{
|
||||
if (BytesReturned == sizeof(HANDLE))
|
||||
ChannelEvent = *(HANDLE*)buffer;
|
||||
|
||||
WTSFreeMemory(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
nCount = 0;
|
||||
events[nCount++] = audin->stopEvent;
|
||||
events[nCount++] = ChannelEvent;
|
||||
|
||||
/* Wait for the client to confirm that the Audio Input dynamic channel is ready */
|
||||
|
||||
while (1)
|
||||
{
|
||||
status = WaitForMultipleObjects(nCount, events, FALSE, 100);
|
||||
|
||||
if (status == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_Print(audin->log, WLOG_ERROR,
|
||||
"WaitForMultipleObjects failed with error %" PRIu32 "", error);
|
||||
goto out;
|
||||
}
|
||||
if (status == WAIT_OBJECT_0)
|
||||
goto out;
|
||||
|
||||
if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, &buffer,
|
||||
&BytesReturned) == FALSE)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ready = *((BOOL*)buffer);
|
||||
WTSFreeMemory(buffer);
|
||||
|
||||
if (ready)
|
||||
break;
|
||||
}
|
||||
|
||||
s = Stream_New(nullptr, 4096);
|
||||
|
||||
if (!s)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!");
|
||||
error = CHANNEL_RC_NO_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ready)
|
||||
{
|
||||
SNDIN_VERSION version = WINPR_C_ARRAY_INIT;
|
||||
|
||||
version.Version = audin->context.serverVersion;
|
||||
|
||||
if ((error = audin->context.SendVersion(&audin->context, &version)))
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "SendVersion failed with error %" PRIu32 "!", error);
|
||||
goto out_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
while (ready)
|
||||
{
|
||||
SNDIN_PDU header = WINPR_C_ARRAY_INIT;
|
||||
|
||||
if ((status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE)) == WAIT_OBJECT_0)
|
||||
break;
|
||||
|
||||
if (status == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_Print(audin->log, WLOG_ERROR,
|
||||
"WaitForMultipleObjects failed with error %" PRIu32 "", error);
|
||||
break;
|
||||
}
|
||||
if (status == WAIT_OBJECT_0)
|
||||
break;
|
||||
|
||||
Stream_ResetPosition(s);
|
||||
|
||||
if (!WTSVirtualChannelRead(audin->audin_channel, 0, nullptr, 0, &BytesReturned))
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (BytesReturned < 1)
|
||||
continue;
|
||||
|
||||
if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
|
||||
break;
|
||||
|
||||
WINPR_ASSERT(Stream_Capacity(s) <= UINT32_MAX);
|
||||
if (WTSVirtualChannelRead(audin->audin_channel, 0, Stream_BufferAs(s, char),
|
||||
(ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
Stream_SetLength(s, BytesReturned);
|
||||
if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, SNDIN_HEADER_SIZE))
|
||||
{
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
Stream_Read_UINT8(s, header.MessageId);
|
||||
|
||||
switch (header.MessageId)
|
||||
{
|
||||
case MSG_SNDIN_VERSION:
|
||||
error = audin_server_recv_version(&audin->context, s, &header);
|
||||
break;
|
||||
case MSG_SNDIN_FORMATS:
|
||||
error = audin_server_recv_formats(&audin->context, s, &header);
|
||||
break;
|
||||
case MSG_SNDIN_OPEN_REPLY:
|
||||
error = audin_server_recv_open_reply(&audin->context, s, &header);
|
||||
break;
|
||||
case MSG_SNDIN_DATA_INCOMING:
|
||||
error = audin_server_recv_data_incoming(&audin->context, s, &header);
|
||||
break;
|
||||
case MSG_SNDIN_DATA:
|
||||
error = audin_server_recv_data(&audin->context, s, &header);
|
||||
break;
|
||||
case MSG_SNDIN_FORMATCHANGE:
|
||||
error = audin_server_recv_format_change(&audin->context, s, &header);
|
||||
break;
|
||||
default:
|
||||
WLog_Print(audin->log, WLOG_ERROR,
|
||||
"audin_server_thread_func: unknown or invalid MessageId %" PRIu8 "",
|
||||
header.MessageId);
|
||||
error = ERROR_INVALID_DATA;
|
||||
break;
|
||||
}
|
||||
if (error)
|
||||
break;
|
||||
}
|
||||
|
||||
out_capacity:
|
||||
Stream_Free(s, TRUE);
|
||||
out:
|
||||
(void)WTSVirtualChannelClose(audin->audin_channel);
|
||||
audin->audin_channel = nullptr;
|
||||
|
||||
if (error && audin->context.rdpcontext)
|
||||
setChannelError(audin->context.rdpcontext, error,
|
||||
"audin_server_thread_func reported an error");
|
||||
|
||||
ExitThread(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
static BOOL audin_server_open(audin_server_context* context)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
|
||||
WINPR_ASSERT(audin);
|
||||
if (!audin->thread)
|
||||
{
|
||||
PULONG pSessionId = nullptr;
|
||||
DWORD BytesReturned = 0;
|
||||
audin->SessionId = WTS_CURRENT_SESSION;
|
||||
UINT32 channelId = 0;
|
||||
BOOL status = TRUE;
|
||||
|
||||
if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
|
||||
(LPSTR*)&pSessionId, &BytesReturned))
|
||||
{
|
||||
audin->SessionId = (DWORD)*pSessionId;
|
||||
WTSFreeMemory(pSessionId);
|
||||
}
|
||||
|
||||
audin->audin_channel = WTSVirtualChannelOpenEx(audin->SessionId, AUDIN_DVC_CHANNEL_NAME,
|
||||
WTS_CHANNEL_OPTION_DYNAMIC);
|
||||
|
||||
if (!audin->audin_channel)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelOpenEx failed!");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
channelId = WTSChannelGetIdByHandle(audin->audin_channel);
|
||||
|
||||
IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
|
||||
if (!status)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "context->ChannelIdAssigned failed!");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!(audin->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "CreateEvent failed!");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!(audin->thread =
|
||||
CreateThread(nullptr, 0, audin_server_thread_func, (void*)audin, 0, nullptr)))
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "CreateThread failed!");
|
||||
(void)CloseHandle(audin->stopEvent);
|
||||
audin->stopEvent = nullptr;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
WLog_Print(audin->log, WLOG_ERROR, "thread already running!");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOL audin_server_is_open(audin_server_context* context)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
|
||||
WINPR_ASSERT(audin);
|
||||
return audin->thread != nullptr;
|
||||
}
|
||||
|
||||
static BOOL audin_server_close(audin_server_context* context)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
WINPR_ASSERT(audin);
|
||||
|
||||
if (audin->thread)
|
||||
{
|
||||
(void)SetEvent(audin->stopEvent);
|
||||
|
||||
if (WaitForSingleObject(audin->thread, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "",
|
||||
GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
(void)CloseHandle(audin->thread);
|
||||
(void)CloseHandle(audin->stopEvent);
|
||||
audin->thread = nullptr;
|
||||
audin->stopEvent = nullptr;
|
||||
}
|
||||
|
||||
if (audin->audin_channel)
|
||||
{
|
||||
(void)WTSVirtualChannelClose(audin->audin_channel);
|
||||
audin->audin_channel = nullptr;
|
||||
}
|
||||
|
||||
audin->audin_negotiated_format = nullptr;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static wStream* audin_server_packet_new(wLog* log, size_t size, BYTE MessageId)
|
||||
{
|
||||
WINPR_ASSERT(log);
|
||||
|
||||
/* Allocate what we need plus header bytes */
|
||||
wStream* s = Stream_New(nullptr, size + SNDIN_HEADER_SIZE);
|
||||
if (!s)
|
||||
{
|
||||
WLog_Print(log, WLOG_ERROR, "Stream_New failed!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Stream_Write_UINT8(s, MessageId);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static UINT audin_server_packet_send(audin_server_context* context, wStream* s)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
ULONG written = 0;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
const size_t pos = Stream_GetPosition(s);
|
||||
WINPR_ASSERT(pos <= UINT32_MAX);
|
||||
if (!WTSVirtualChannelWrite(audin->audin_channel, Stream_BufferAs(s, char), (UINT32)pos,
|
||||
&written))
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelWrite failed!");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (written < Stream_GetPosition(s))
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_WARN, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "",
|
||||
written, Stream_GetPosition(s));
|
||||
}
|
||||
|
||||
out:
|
||||
Stream_Free(s, TRUE);
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT audin_server_send_version(audin_server_context* context, const SNDIN_VERSION* version)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(version);
|
||||
|
||||
wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_VERSION);
|
||||
if (!s)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
Stream_Write_UINT32(s, version->Version);
|
||||
|
||||
return audin_server_packet_send(context, s);
|
||||
}
|
||||
|
||||
static UINT audin_server_send_formats(audin_server_context* context, const SNDIN_FORMATS* formats)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
|
||||
WINPR_ASSERT(audin);
|
||||
WINPR_ASSERT(formats);
|
||||
|
||||
wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18, MSG_SNDIN_FORMATS);
|
||||
if (!s)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
Stream_Write_UINT32(s, formats->NumFormats);
|
||||
Stream_Write_UINT32(s, formats->cbSizeFormatsPacket);
|
||||
|
||||
for (UINT32 i = 0; i < formats->NumFormats; ++i)
|
||||
{
|
||||
AUDIO_FORMAT* format = &formats->SoundFormats[i];
|
||||
|
||||
if (!audio_format_write(s, format))
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "Failed to write audio format");
|
||||
Stream_Free(s, TRUE);
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
return audin_server_packet_send(context, s);
|
||||
}
|
||||
|
||||
static UINT audin_server_send_open(audin_server_context* context, const SNDIN_OPEN* open)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
WINPR_ASSERT(audin);
|
||||
WINPR_ASSERT(open);
|
||||
|
||||
wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18 + 22, MSG_SNDIN_OPEN);
|
||||
if (!s)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
Stream_Write_UINT32(s, open->FramesPerPacket);
|
||||
Stream_Write_UINT32(s, open->initialFormat);
|
||||
|
||||
Stream_Write_UINT16(s, open->captureFormat.wFormatTag);
|
||||
Stream_Write_UINT16(s, open->captureFormat.nChannels);
|
||||
Stream_Write_UINT32(s, open->captureFormat.nSamplesPerSec);
|
||||
Stream_Write_UINT32(s, open->captureFormat.nAvgBytesPerSec);
|
||||
Stream_Write_UINT16(s, open->captureFormat.nBlockAlign);
|
||||
Stream_Write_UINT16(s, open->captureFormat.wBitsPerSample);
|
||||
|
||||
if (open->ExtraFormatData)
|
||||
{
|
||||
Stream_Write_UINT16(s, 22); /* cbSize */
|
||||
|
||||
Stream_Write_UINT16(s, open->ExtraFormatData->Samples.wReserved);
|
||||
Stream_Write_UINT32(s, open->ExtraFormatData->dwChannelMask);
|
||||
|
||||
Stream_Write_UINT32(s, open->ExtraFormatData->SubFormat.Data1);
|
||||
Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data2);
|
||||
Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data3);
|
||||
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[0]);
|
||||
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[1]);
|
||||
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[2]);
|
||||
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[3]);
|
||||
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[4]);
|
||||
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[5]);
|
||||
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[6]);
|
||||
Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[7]);
|
||||
}
|
||||
else
|
||||
{
|
||||
WINPR_ASSERT(open->captureFormat.wFormatTag != WAVE_FORMAT_EXTENSIBLE);
|
||||
|
||||
Stream_Write_UINT16(s, 0); /* cbSize */
|
||||
}
|
||||
|
||||
return audin_server_packet_send(context, s);
|
||||
}
|
||||
|
||||
static UINT audin_server_send_format_change(audin_server_context* context,
|
||||
const SNDIN_FORMATCHANGE* format_change)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(format_change);
|
||||
|
||||
wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_FORMATCHANGE);
|
||||
if (!s)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
Stream_Write_UINT32(s, format_change->NewFormat);
|
||||
|
||||
return audin_server_packet_send(context, s);
|
||||
}
|
||||
|
||||
static UINT audin_server_receive_version_default(audin_server_context* audin_ctx,
|
||||
const SNDIN_VERSION* version)
|
||||
{
|
||||
audin_server* audin = (audin_server*)audin_ctx;
|
||||
SNDIN_FORMATS formats = WINPR_C_ARRAY_INIT;
|
||||
|
||||
WINPR_ASSERT(audin);
|
||||
WINPR_ASSERT(version);
|
||||
|
||||
if (version->Version == 0)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR, "Received invalid AUDIO_INPUT version from client");
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
WLog_Print(audin->log, WLOG_DEBUG, "AUDIO_INPUT version of client: %u", version->Version);
|
||||
|
||||
formats.NumFormats = audin->audin_n_server_formats;
|
||||
formats.SoundFormats = audin->audin_server_formats;
|
||||
|
||||
return audin->context.SendFormats(&audin->context, &formats);
|
||||
}
|
||||
|
||||
static UINT send_open(audin_server* audin)
|
||||
{
|
||||
SNDIN_OPEN open = WINPR_C_ARRAY_INIT;
|
||||
|
||||
WINPR_ASSERT(audin);
|
||||
|
||||
open.FramesPerPacket = 441;
|
||||
open.initialFormat = audin->audin_client_format_idx;
|
||||
open.captureFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
open.captureFormat.nChannels = 2;
|
||||
open.captureFormat.nSamplesPerSec = 44100;
|
||||
open.captureFormat.nAvgBytesPerSec = 44100 * 2 * 2;
|
||||
open.captureFormat.nBlockAlign = 4;
|
||||
open.captureFormat.wBitsPerSample = 16;
|
||||
|
||||
WINPR_ASSERT(audin->context.SendOpen);
|
||||
return audin->context.SendOpen(&audin->context, &open);
|
||||
}
|
||||
|
||||
static UINT audin_server_receive_formats_default(audin_server_context* context,
|
||||
const SNDIN_FORMATS* formats)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
WINPR_ASSERT(audin);
|
||||
WINPR_ASSERT(formats);
|
||||
|
||||
if (audin->audin_negotiated_format)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR,
|
||||
"Received client formats, but negotiation was already done");
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
for (UINT32 i = 0; i < audin->audin_n_server_formats; ++i)
|
||||
{
|
||||
for (UINT32 j = 0; j < formats->NumFormats; ++j)
|
||||
{
|
||||
if (audio_format_compatible(&audin->audin_server_formats[i], &formats->SoundFormats[j]))
|
||||
{
|
||||
audin->audin_negotiated_format = &audin->audin_server_formats[i];
|
||||
audin->audin_client_format_idx = i;
|
||||
return send_open(audin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WLog_Print(audin->log, WLOG_ERROR, "Could not agree on a audio format with the server");
|
||||
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
static UINT audin_server_receive_format_change_default(audin_server_context* context,
|
||||
const SNDIN_FORMATCHANGE* format_change)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
|
||||
WINPR_ASSERT(audin);
|
||||
WINPR_ASSERT(format_change);
|
||||
|
||||
if (format_change->NewFormat != audin->audin_client_format_idx)
|
||||
{
|
||||
WLog_Print(audin->log, WLOG_ERROR,
|
||||
"NewFormat in FormatChange differs from requested format");
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
WLog_Print(audin->log, WLOG_DEBUG, "Received Format Change PDU: %u", format_change->NewFormat);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static UINT
|
||||
audin_server_incoming_data_default(audin_server_context* context,
|
||||
WINPR_ATTR_UNUSED const SNDIN_DATA_INCOMING* data_incoming)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
WINPR_ASSERT(audin);
|
||||
WINPR_ASSERT(data_incoming);
|
||||
|
||||
/* TODO: Implement bandwidth measure of clients uplink */
|
||||
WLog_Print(audin->log, WLOG_DEBUG, "Received Incoming Data PDU");
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static UINT audin_server_open_reply_default(audin_server_context* context,
|
||||
const SNDIN_OPEN_REPLY* open_reply)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
WINPR_ASSERT(audin);
|
||||
WINPR_ASSERT(open_reply);
|
||||
|
||||
/* TODO: Implement failure handling */
|
||||
WLog_Print(audin->log, WLOG_DEBUG, "Open Reply PDU: Result: %" PRIu32, open_reply->Result);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
audin_server_context* audin_server_context_new(HANDLE vcm)
|
||||
{
|
||||
audin_server* audin = (audin_server*)calloc(1, sizeof(audin_server));
|
||||
|
||||
if (!audin)
|
||||
{
|
||||
WLog_ERR(AUDIN_TAG, "calloc failed!");
|
||||
return nullptr;
|
||||
}
|
||||
audin->log = WLog_Get(AUDIN_TAG);
|
||||
audin->context.vcm = vcm;
|
||||
audin->context.Open = audin_server_open;
|
||||
audin->context.IsOpen = audin_server_is_open;
|
||||
audin->context.Close = audin_server_close;
|
||||
|
||||
audin->context.SendVersion = audin_server_send_version;
|
||||
audin->context.SendFormats = audin_server_send_formats;
|
||||
audin->context.SendOpen = audin_server_send_open;
|
||||
audin->context.SendFormatChange = audin_server_send_format_change;
|
||||
|
||||
/* Default values */
|
||||
audin->context.serverVersion = SNDIN_VERSION_Version_2;
|
||||
audin->context.ReceiveVersion = audin_server_receive_version_default;
|
||||
audin->context.ReceiveFormats = audin_server_receive_formats_default;
|
||||
audin->context.ReceiveFormatChange = audin_server_receive_format_change_default;
|
||||
audin->context.IncomingData = audin_server_incoming_data_default;
|
||||
audin->context.OpenReply = audin_server_open_reply_default;
|
||||
|
||||
return &audin->context;
|
||||
}
|
||||
|
||||
void audin_server_context_free(audin_server_context* context)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
|
||||
if (!audin)
|
||||
return;
|
||||
|
||||
audin_server_close(context);
|
||||
audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats);
|
||||
audin->audin_server_formats = nullptr;
|
||||
free(audin);
|
||||
}
|
||||
|
||||
BOOL audin_server_set_formats(audin_server_context* context, SSIZE_T count,
|
||||
const AUDIO_FORMAT* formats)
|
||||
{
|
||||
audin_server* audin = (audin_server*)context;
|
||||
WINPR_ASSERT(audin);
|
||||
|
||||
audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats);
|
||||
audin->audin_n_server_formats = 0;
|
||||
audin->audin_server_formats = nullptr;
|
||||
audin->audin_negotiated_format = nullptr;
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
const size_t audin_n_server_formats =
|
||||
server_audin_get_formats(&audin->audin_server_formats);
|
||||
WINPR_ASSERT(audin_n_server_formats <= UINT32_MAX);
|
||||
|
||||
audin->audin_n_server_formats = (UINT32)audin_n_server_formats;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t scount = (size_t)count;
|
||||
AUDIO_FORMAT* audin_server_formats = audio_formats_new(scount);
|
||||
if (!audin_server_formats)
|
||||
return count == 0;
|
||||
|
||||
for (SSIZE_T x = 0; x < count; x++)
|
||||
{
|
||||
if (!audio_format_copy(&formats[x], &audin_server_formats[x]))
|
||||
{
|
||||
audio_formats_free(audin_server_formats, scount);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
WINPR_ASSERT(count <= UINT32_MAX);
|
||||
audin->audin_server_formats = audin_server_formats;
|
||||
audin->audin_n_server_formats = (UINT32)count;
|
||||
}
|
||||
return audin->audin_n_server_formats > 0;
|
||||
}
|
||||
|
||||
const AUDIO_FORMAT* audin_server_get_negotiated_format(const audin_server_context* context)
|
||||
{
|
||||
const audin_server* audin = (const audin_server*)context;
|
||||
WINPR_ASSERT(audin);
|
||||
|
||||
return audin->audin_negotiated_format;
|
||||
}
|
||||
Reference in New Issue
Block a user