summaryrefslogtreecommitdiffstats
path: root/channels/tsmf/client/tsmf_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'channels/tsmf/client/tsmf_main.c')
-rw-r--r--channels/tsmf/client/tsmf_main.c609
1 files changed, 609 insertions, 0 deletions
diff --git a/channels/tsmf/client/tsmf_main.c b/channels/tsmf/client/tsmf_main.c
new file mode 100644
index 0000000..877ef24
--- /dev/null
+++ b/channels/tsmf/client/tsmf_main.c
@@ -0,0 +1,609 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+#include <winpr/cmdline.h>
+
+#include <freerdp/client/tsmf.h>
+
+#include "tsmf_types.h"
+#include "tsmf_constants.h"
+#include "tsmf_ifman.h"
+#include "tsmf_media.h"
+
+#include "tsmf_main.h"
+
+BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id)
+{
+ wStream* s = NULL;
+ int status = -1;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
+
+ if (!callback)
+ {
+ DEBUG_TSMF("No callback reference - unable to send eos response!");
+ return FALSE;
+ }
+
+ if (callback && callback->stream_id && callback->channel && callback->channel->Write)
+ {
+ s = Stream_New(NULL, 24);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY);
+ Stream_Write_UINT32(s, message_id);
+ Stream_Write_UINT32(s, CLIENT_EVENT_NOTIFICATION); /* FunctionId */
+ Stream_Write_UINT32(s, callback->stream_id); /* StreamId */
+ Stream_Write_UINT32(s, TSMM_CLIENT_EVENT_ENDOFSTREAM); /* EventId */
+ Stream_Write_UINT32(s, 0); /* cbData */
+ DEBUG_TSMF("EOS response size %" PRIuz "", Stream_GetPosition(s));
+ status = callback->channel->Write(callback->channel, Stream_GetPosition(s),
+ Stream_Buffer(s), NULL);
+
+ if (status)
+ {
+ WLog_ERR(TAG, "response error %d", status);
+ }
+
+ Stream_Free(s, TRUE);
+ }
+
+ return (status == 0);
+}
+
+BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id,
+ UINT64 duration, UINT32 data_size)
+{
+ wStream* s = NULL;
+ int status = -1;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
+
+ if (!callback)
+ return FALSE;
+
+ s = Stream_New(NULL, 32);
+
+ if (!s)
+ return FALSE;
+
+ Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY);
+ Stream_Write_UINT32(s, message_id);
+ Stream_Write_UINT32(s, PLAYBACK_ACK); /* FunctionId */
+ Stream_Write_UINT32(s, callback->stream_id); /* StreamId */
+ Stream_Write_UINT64(s, duration); /* DataDuration */
+ Stream_Write_UINT64(s, data_size); /* cbData */
+ DEBUG_TSMF("ACK response size %" PRIuz "", Stream_GetPosition(s));
+
+ if (!callback->channel || !callback->channel->Write)
+ {
+ WLog_ERR(TAG, "callback=%p, channel=%p, write=%p", callback,
+ (callback ? callback->channel : NULL),
+ (callback && callback->channel ? callback->channel->Write : NULL));
+ }
+ else
+ {
+ status = callback->channel->Write(callback->channel, Stream_GetPosition(s),
+ Stream_Buffer(s), NULL);
+ }
+
+ if (status)
+ {
+ WLog_ERR(TAG, "response error %d", status);
+ }
+
+ Stream_Free(s, TRUE);
+ return (status == 0);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ size_t length = 0;
+ wStream* input = NULL;
+ wStream* output = NULL;
+ UINT error = CHANNEL_RC_OK;
+ BOOL processed = FALSE;
+ TSMF_IFMAN ifman = { 0 };
+ UINT32 MessageId = 0;
+ UINT32 FunctionId = 0;
+ UINT32 InterfaceId = 0;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
+ UINT32 cbSize = Stream_GetRemainingLength(data);
+
+ /* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 12))
+ return ERROR_INVALID_DATA;
+
+ input = data;
+ output = Stream_New(NULL, 256);
+
+ if (!output)
+ return ERROR_OUTOFMEMORY;
+
+ Stream_Seek(output, 8);
+ Stream_Read_UINT32(input, InterfaceId); /* InterfaceId (4 bytes) */
+ Stream_Read_UINT32(input, MessageId); /* MessageId (4 bytes) */
+ Stream_Read_UINT32(input, FunctionId); /* FunctionId (4 bytes) */
+ DEBUG_TSMF("cbSize=%" PRIu32 " InterfaceId=0x%" PRIX32 " MessageId=0x%" PRIX32
+ " FunctionId=0x%" PRIX32 "",
+ cbSize, InterfaceId, MessageId, FunctionId);
+ ifman.channel_callback = pChannelCallback;
+ ifman.decoder_name = ((TSMF_PLUGIN*)callback->plugin)->decoder_name;
+ ifman.audio_name = ((TSMF_PLUGIN*)callback->plugin)->audio_name;
+ ifman.audio_device = ((TSMF_PLUGIN*)callback->plugin)->audio_device;
+ CopyMemory(ifman.presentation_id, callback->presentation_id, GUID_SIZE);
+ ifman.stream_id = callback->stream_id;
+ ifman.message_id = MessageId;
+ ifman.input = input;
+ ifman.input_size = cbSize - 12;
+ ifman.output = output;
+ ifman.output_pending = FALSE;
+ ifman.output_interface_id = InterfaceId;
+
+ // fprintf(stderr, "InterfaceId: 0x%08"PRIX32" MessageId: 0x%08"PRIX32" FunctionId:
+ // 0x%08"PRIX32"\n", InterfaceId, MessageId, FunctionId);
+
+ switch (InterfaceId)
+ {
+ case TSMF_INTERFACE_CAPABILITIES | STREAM_ID_NONE:
+ switch (FunctionId)
+ {
+ case RIM_EXCHANGE_CAPABILITY_REQUEST:
+ error = tsmf_ifman_rim_exchange_capability_request(&ifman);
+ processed = TRUE;
+ break;
+
+ case RIMCALL_RELEASE:
+ case RIMCALL_QUERYINTERFACE:
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case TSMF_INTERFACE_DEFAULT | STREAM_ID_PROXY:
+ switch (FunctionId)
+ {
+ case SET_CHANNEL_PARAMS:
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, GUID_SIZE + 4))
+ {
+ error = ERROR_INVALID_DATA;
+ goto out;
+ }
+
+ CopyMemory(callback->presentation_id, Stream_Pointer(input), GUID_SIZE);
+ Stream_Seek(input, GUID_SIZE);
+ Stream_Read_UINT32(input, callback->stream_id);
+ DEBUG_TSMF("SET_CHANNEL_PARAMS StreamId=%" PRIu32 "", callback->stream_id);
+ ifman.output_pending = TRUE;
+ processed = TRUE;
+ break;
+
+ case EXCHANGE_CAPABILITIES_REQ:
+ error = tsmf_ifman_exchange_capability_request(&ifman);
+ processed = TRUE;
+ break;
+
+ case CHECK_FORMAT_SUPPORT_REQ:
+ error = tsmf_ifman_check_format_support_request(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_NEW_PRESENTATION:
+ error = tsmf_ifman_on_new_presentation(&ifman);
+ processed = TRUE;
+ break;
+
+ case ADD_STREAM:
+ error =
+ tsmf_ifman_add_stream(&ifman, ((TSMF_PLUGIN*)callback->plugin)->rdpcontext);
+ processed = TRUE;
+ break;
+
+ case SET_TOPOLOGY_REQ:
+ error = tsmf_ifman_set_topology_request(&ifman);
+ processed = TRUE;
+ break;
+
+ case REMOVE_STREAM:
+ error = tsmf_ifman_remove_stream(&ifman);
+ processed = TRUE;
+ break;
+
+ case SET_SOURCE_VIDEO_RECT:
+ error = tsmf_ifman_set_source_video_rect(&ifman);
+ processed = TRUE;
+ break;
+
+ case SHUTDOWN_PRESENTATION_REQ:
+ error = tsmf_ifman_shutdown_presentation(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_STREAM_VOLUME:
+ error = tsmf_ifman_on_stream_volume(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_CHANNEL_VOLUME:
+ error = tsmf_ifman_on_channel_volume(&ifman);
+ processed = TRUE;
+ break;
+
+ case SET_VIDEO_WINDOW:
+ error = tsmf_ifman_set_video_window(&ifman);
+ processed = TRUE;
+ break;
+
+ case UPDATE_GEOMETRY_INFO:
+ error = tsmf_ifman_update_geometry_info(&ifman);
+ processed = TRUE;
+ break;
+
+ case SET_ALLOCATOR:
+ error = tsmf_ifman_set_allocator(&ifman);
+ processed = TRUE;
+ break;
+
+ case NOTIFY_PREROLL:
+ error = tsmf_ifman_notify_preroll(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_SAMPLE:
+ error = tsmf_ifman_on_sample(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_FLUSH:
+ error = tsmf_ifman_on_flush(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_END_OF_STREAM:
+ error = tsmf_ifman_on_end_of_stream(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_STARTED:
+ error = tsmf_ifman_on_playback_started(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_PAUSED:
+ error = tsmf_ifman_on_playback_paused(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_RESTARTED:
+ error = tsmf_ifman_on_playback_restarted(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_STOPPED:
+ error = tsmf_ifman_on_playback_stopped(&ifman);
+ processed = TRUE;
+ break;
+
+ case ON_PLAYBACK_RATE_CHANGED:
+ error = tsmf_ifman_on_playback_rate_changed(&ifman);
+ processed = TRUE;
+ break;
+
+ case RIMCALL_RELEASE:
+ case RIMCALL_QUERYINTERFACE:
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ input = NULL;
+ ifman.input = NULL;
+
+ if (error)
+ {
+ WLog_ERR(TAG, "ifman data received processing error %" PRIu32 "", error);
+ }
+
+ if (!processed)
+ {
+ switch (FunctionId)
+ {
+ case RIMCALL_RELEASE:
+ /* [MS-RDPEXPS] 2.2.2.2 Interface Release (IFACE_RELEASE)
+ This message does not require a reply. */
+ processed = TRUE;
+ ifman.output_pending = 1;
+ break;
+
+ case RIMCALL_QUERYINTERFACE:
+ /* [MS-RDPEXPS] 2.2.2.1.2 Query Interface Response (QI_RSP)
+ This message is not supported in this channel. */
+ processed = TRUE;
+ break;
+ }
+
+ if (!processed)
+ {
+ WLog_ERR(TAG,
+ "Unknown InterfaceId: 0x%08" PRIX32 " MessageId: 0x%08" PRIX32
+ " FunctionId: 0x%08" PRIX32 "\n",
+ InterfaceId, MessageId, FunctionId);
+ /* When a request is not implemented we return empty response indicating error */
+ }
+
+ processed = TRUE;
+ }
+
+ if (processed && !ifman.output_pending)
+ {
+ /* Response packet does not have FunctionId */
+ length = Stream_GetPosition(output);
+ Stream_SetPosition(output, 0);
+ Stream_Write_UINT32(output, ifman.output_interface_id);
+ Stream_Write_UINT32(output, MessageId);
+ DEBUG_TSMF("response size %d", length);
+ error = callback->channel->Write(callback->channel, length, Stream_Buffer(output), NULL);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "response error %" PRIu32 "", error);
+ }
+ }
+
+out:
+ Stream_Free(output, TRUE);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_on_close(IWTSVirtualChannelCallback* pChannelCallback)
+{
+ TSMF_STREAM* stream = NULL;
+ TSMF_PRESENTATION* presentation = NULL;
+ TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
+ DEBUG_TSMF("");
+
+ if (callback->stream_id)
+ {
+ presentation = tsmf_presentation_find_by_id(callback->presentation_id);
+
+ if (presentation)
+ {
+ stream = tsmf_stream_find_by_id(presentation, callback->stream_id);
+
+ if (stream)
+ tsmf_stream_free(stream);
+ }
+ }
+
+ free(pChannelCallback);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
+ IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept,
+ IWTSVirtualChannelCallback** ppCallback)
+{
+ TSMF_CHANNEL_CALLBACK* callback = NULL;
+ TSMF_LISTENER_CALLBACK* listener_callback = (TSMF_LISTENER_CALLBACK*)pListenerCallback;
+ DEBUG_TSMF("");
+ callback = (TSMF_CHANNEL_CALLBACK*)calloc(1, sizeof(TSMF_CHANNEL_CALLBACK));
+
+ if (!callback)
+ return CHANNEL_RC_NO_MEMORY;
+
+ callback->iface.OnDataReceived = tsmf_on_data_received;
+ callback->iface.OnClose = tsmf_on_close;
+ callback->iface.OnOpen = NULL;
+ callback->plugin = listener_callback->plugin;
+ callback->channel_mgr = listener_callback->channel_mgr;
+ callback->channel = pChannel;
+ *ppCallback = (IWTSVirtualChannelCallback*)callback;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
+{
+ UINT status = 0;
+ TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
+ DEBUG_TSMF("");
+ tsmf->listener_callback = (TSMF_LISTENER_CALLBACK*)calloc(1, sizeof(TSMF_LISTENER_CALLBACK));
+
+ if (!tsmf->listener_callback)
+ return CHANNEL_RC_NO_MEMORY;
+
+ tsmf->listener_callback->iface.OnNewChannelConnection = tsmf_on_new_channel_connection;
+ tsmf->listener_callback->plugin = pPlugin;
+ tsmf->listener_callback->channel_mgr = pChannelMgr;
+ status = pChannelMgr->CreateListener(
+ pChannelMgr, "TSMF", 0, (IWTSListenerCallback*)tsmf->listener_callback, &(tsmf->listener));
+ tsmf->listener->pInterface = tsmf->iface.pInterface;
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
+ DEBUG_TSMF("");
+ free(tsmf->listener_callback);
+ free(tsmf);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT tsmf_process_addin_args(IWTSPlugin* pPlugin, const ADDIN_ARGV* args)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
+ COMMAND_LINE_ARGUMENT_A tsmf_args[] = { { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
+ NULL, NULL, -1, NULL, "audio subsystem" },
+ { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL,
+ NULL, -1, NULL, "audio device name" },
+ { "decoder", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
+ NULL, NULL, -1, NULL, "decoder subsystem" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
+ flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
+ status = CommandLineParseArgumentsA(args->argc, args->argv, tsmf_args, flags, tsmf, NULL, NULL);
+
+ if (status != 0)
+ return ERROR_INVALID_DATA;
+
+ arg = tsmf_args;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys")
+ {
+ tsmf->audio_name = _strdup(arg->Value);
+
+ if (!tsmf->audio_name)
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchCase(arg, "dev")
+ {
+ tsmf->audio_device = _strdup(arg->Value);
+
+ if (!tsmf->audio_device)
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchCase(arg, "decoder")
+ {
+ tsmf->decoder_name = _strdup(arg->Value);
+
+ if (!tsmf->decoder_name)
+ return ERROR_OUTOFMEMORY;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT tsmf_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ UINT status = 0;
+ TSMF_PLUGIN* tsmf = NULL;
+ TsmfClientContext* context = NULL;
+ UINT error = CHANNEL_RC_NO_MEMORY;
+ tsmf = (TSMF_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "tsmf");
+
+ if (!tsmf)
+ {
+ tsmf = (TSMF_PLUGIN*)calloc(1, sizeof(TSMF_PLUGIN));
+
+ if (!tsmf)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ tsmf->iface.Initialize = tsmf_plugin_initialize;
+ tsmf->iface.Connected = NULL;
+ tsmf->iface.Disconnected = NULL;
+ tsmf->iface.Terminated = tsmf_plugin_terminated;
+ tsmf->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
+ context = (TsmfClientContext*)calloc(1, sizeof(TsmfClientContext));
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ goto error_context;
+ }
+
+ context->handle = (void*)tsmf;
+ tsmf->iface.pInterface = (void*)context;
+
+ if (!tsmf_media_init())
+ {
+ error = ERROR_INVALID_OPERATION;
+ goto error_init;
+ }
+
+ status = pEntryPoints->RegisterPlugin(pEntryPoints, "tsmf", &tsmf->iface);
+ }
+
+ if (status == CHANNEL_RC_OK)
+ {
+ status = tsmf_process_addin_args(&tsmf->iface, pEntryPoints->GetPluginData(pEntryPoints));
+ }
+
+ return status;
+error_init:
+ free(context);
+error_context:
+ free(tsmf);
+ return error;
+}