/** * 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 * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #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; wStream* input; wStream* output; UINT error = CHANNEL_RC_OK; BOOL processed = FALSE; TSMF_IFMAN ifman; UINT32 MessageId; UINT32 FunctionId; UINT32 InterfaceId; TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; UINT32 cbSize = Stream_GetRemainingLength(data); /* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */ if (cbSize < 12) { WLog_ERR(TAG, "invalid size. cbSize=%"PRIu32"", cbSize); 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); ZeroMemory(&ifman, sizeof(TSMF_IFMAN)); 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_GetRemainingLength(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; TSMF_PRESENTATION* presentation; 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; 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; 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, ADDIN_ARGV* args) { int status; DWORD flags; COMMAND_LINE_ARGUMENT_A* arg; TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*) pPlugin; COMMAND_LINE_ARGUMENT_A tsmf_args[] = { { "sys", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio subsystem" }, { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio device name" }, { "decoder", COMMAND_LINE_VALUE_REQUIRED, "", 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; } #ifdef BUILTIN_CHANNELS #define DVCPluginEntry tsmf_DVCPluginEntry #else #define DVCPluginEntry FREERDP_API DVCPluginEntry #endif /** * Function description * * @return 0 on success, otherwise a Win32 error code */ UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { UINT status = 0; TSMF_PLUGIN* tsmf; TsmfClientContext* context; 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 = ((freerdp*)((rdpSettings*) pEntryPoints->GetRdpSettings( pEntryPoints))->instance)->context; 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", (IWTSPlugin*) tsmf); } if (status == CHANNEL_RC_OK) { status = tsmf_process_addin_args((IWTSPlugin*) tsmf, pEntryPoints->GetPluginData(pEntryPoints)); } return status; error_init: free(context); error_context: free(tsmf); return error; }