1 /**
2  * FreeRDP: A Remote Desktop Protocol Implementation
3  * Video Redirection Virtual Channel
4  *
5  * Copyright 2010-2011 Vic Lee
6  * Copyright 2015 Thincast Technologies GmbH
7  * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *     http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <winpr/crt.h>
27 #include <winpr/stream.h>
28 #include <winpr/cmdline.h>
29 
30 #include <freerdp/client/tsmf.h>
31 
32 #include "tsmf_types.h"
33 #include "tsmf_constants.h"
34 #include "tsmf_ifman.h"
35 #include "tsmf_media.h"
36 
37 #include "tsmf_main.h"
38 
tsmf_send_eos_response(IWTSVirtualChannelCallback * pChannelCallback,UINT32 message_id)39 BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id)
40 {
41 	wStream* s = NULL;
42 	int status = -1;
43 	TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
44 
45 	if (!callback)
46 	{
47 		DEBUG_TSMF("No callback reference - unable to send eos response!");
48 		return FALSE;
49 	}
50 
51 	if (callback && callback->stream_id && callback->channel && callback->channel->Write)
52 	{
53 		s = Stream_New(NULL, 24);
54 
55 		if (!s)
56 			return FALSE;
57 
58 		Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY);
59 		Stream_Write_UINT32(s, message_id);
60 		Stream_Write_UINT32(s, CLIENT_EVENT_NOTIFICATION);     /* FunctionId */
61 		Stream_Write_UINT32(s, callback->stream_id);           /* StreamId */
62 		Stream_Write_UINT32(s, TSMM_CLIENT_EVENT_ENDOFSTREAM); /* EventId */
63 		Stream_Write_UINT32(s, 0);                             /* cbData */
64 		DEBUG_TSMF("EOS response size %" PRIuz "", Stream_GetPosition(s));
65 		status = callback->channel->Write(callback->channel, Stream_GetPosition(s),
66 		                                  Stream_Buffer(s), NULL);
67 
68 		if (status)
69 		{
70 			WLog_ERR(TAG, "response error %d", status);
71 		}
72 
73 		Stream_Free(s, TRUE);
74 	}
75 
76 	return (status == 0);
77 }
78 
tsmf_playback_ack(IWTSVirtualChannelCallback * pChannelCallback,UINT32 message_id,UINT64 duration,UINT32 data_size)79 BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id,
80                        UINT64 duration, UINT32 data_size)
81 {
82 	wStream* s = NULL;
83 	int status = -1;
84 	TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
85 
86 	if (!callback)
87 		return FALSE;
88 
89 	s = Stream_New(NULL, 32);
90 
91 	if (!s)
92 		return FALSE;
93 
94 	Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY);
95 	Stream_Write_UINT32(s, message_id);
96 	Stream_Write_UINT32(s, PLAYBACK_ACK);        /* FunctionId */
97 	Stream_Write_UINT32(s, callback->stream_id); /* StreamId */
98 	Stream_Write_UINT64(s, duration);            /* DataDuration */
99 	Stream_Write_UINT64(s, data_size);           /* cbData */
100 	DEBUG_TSMF("ACK response size %" PRIuz "", Stream_GetPosition(s));
101 
102 	if (!callback->channel || !callback->channel->Write)
103 	{
104 		WLog_ERR(TAG, "callback=%p, channel=%p, write=%p", callback,
105 		         (callback ? callback->channel : NULL),
106 		         (callback && callback->channel ? callback->channel->Write : NULL));
107 	}
108 	else
109 	{
110 		status = callback->channel->Write(callback->channel, Stream_GetPosition(s),
111 		                                  Stream_Buffer(s), NULL);
112 	}
113 
114 	if (status)
115 	{
116 		WLog_ERR(TAG, "response error %d", status);
117 	}
118 
119 	Stream_Free(s, TRUE);
120 	return (status == 0);
121 }
122 
123 /**
124  * Function description
125  *
126  * @return 0 on success, otherwise a Win32 error code
127  */
tsmf_on_data_received(IWTSVirtualChannelCallback * pChannelCallback,wStream * data)128 static UINT tsmf_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
129 {
130 	size_t length;
131 	wStream* input;
132 	wStream* output;
133 	UINT error = CHANNEL_RC_OK;
134 	BOOL processed = FALSE;
135 	TSMF_IFMAN ifman;
136 	UINT32 MessageId;
137 	UINT32 FunctionId;
138 	UINT32 InterfaceId;
139 	TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
140 	UINT32 cbSize = Stream_GetRemainingLength(data);
141 
142 	/* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */
143 	if (cbSize < 12)
144 	{
145 		WLog_ERR(TAG, "invalid size. cbSize=%" PRIu32 "", cbSize);
146 		return ERROR_INVALID_DATA;
147 	}
148 
149 	input = data;
150 	output = Stream_New(NULL, 256);
151 
152 	if (!output)
153 		return ERROR_OUTOFMEMORY;
154 
155 	Stream_Seek(output, 8);
156 	Stream_Read_UINT32(input, InterfaceId); /* InterfaceId (4 bytes) */
157 	Stream_Read_UINT32(input, MessageId);   /* MessageId (4 bytes) */
158 	Stream_Read_UINT32(input, FunctionId);  /* FunctionId (4 bytes) */
159 	DEBUG_TSMF("cbSize=%" PRIu32 " InterfaceId=0x%" PRIX32 " MessageId=0x%" PRIX32
160 	           " FunctionId=0x%" PRIX32 "",
161 	           cbSize, InterfaceId, MessageId, FunctionId);
162 	ZeroMemory(&ifman, sizeof(TSMF_IFMAN));
163 	ifman.channel_callback = pChannelCallback;
164 	ifman.decoder_name = ((TSMF_PLUGIN*)callback->plugin)->decoder_name;
165 	ifman.audio_name = ((TSMF_PLUGIN*)callback->plugin)->audio_name;
166 	ifman.audio_device = ((TSMF_PLUGIN*)callback->plugin)->audio_device;
167 	CopyMemory(ifman.presentation_id, callback->presentation_id, GUID_SIZE);
168 	ifman.stream_id = callback->stream_id;
169 	ifman.message_id = MessageId;
170 	ifman.input = input;
171 	ifman.input_size = cbSize - 12;
172 	ifman.output = output;
173 	ifman.output_pending = FALSE;
174 	ifman.output_interface_id = InterfaceId;
175 
176 	// fprintf(stderr, "InterfaceId: 0x%08"PRIX32" MessageId: 0x%08"PRIX32" FunctionId:
177 	// 0x%08"PRIX32"\n", InterfaceId, MessageId, FunctionId);
178 
179 	switch (InterfaceId)
180 	{
181 		case TSMF_INTERFACE_CAPABILITIES | STREAM_ID_NONE:
182 			switch (FunctionId)
183 			{
184 				case RIM_EXCHANGE_CAPABILITY_REQUEST:
185 					error = tsmf_ifman_rim_exchange_capability_request(&ifman);
186 					processed = TRUE;
187 					break;
188 
189 				case RIMCALL_RELEASE:
190 				case RIMCALL_QUERYINTERFACE:
191 					break;
192 
193 				default:
194 					break;
195 			}
196 
197 			break;
198 
199 		case TSMF_INTERFACE_DEFAULT | STREAM_ID_PROXY:
200 			switch (FunctionId)
201 			{
202 				case SET_CHANNEL_PARAMS:
203 					if (Stream_GetRemainingLength(input) < GUID_SIZE + 4)
204 					{
205 						error = ERROR_INVALID_DATA;
206 						goto out;
207 					}
208 
209 					CopyMemory(callback->presentation_id, Stream_Pointer(input), GUID_SIZE);
210 					Stream_Seek(input, GUID_SIZE);
211 					Stream_Read_UINT32(input, callback->stream_id);
212 					DEBUG_TSMF("SET_CHANNEL_PARAMS StreamId=%" PRIu32 "", callback->stream_id);
213 					ifman.output_pending = TRUE;
214 					processed = TRUE;
215 					break;
216 
217 				case EXCHANGE_CAPABILITIES_REQ:
218 					error = tsmf_ifman_exchange_capability_request(&ifman);
219 					processed = TRUE;
220 					break;
221 
222 				case CHECK_FORMAT_SUPPORT_REQ:
223 					error = tsmf_ifman_check_format_support_request(&ifman);
224 					processed = TRUE;
225 					break;
226 
227 				case ON_NEW_PRESENTATION:
228 					error = tsmf_ifman_on_new_presentation(&ifman);
229 					processed = TRUE;
230 					break;
231 
232 				case ADD_STREAM:
233 					error =
234 					    tsmf_ifman_add_stream(&ifman, ((TSMF_PLUGIN*)callback->plugin)->rdpcontext);
235 					processed = TRUE;
236 					break;
237 
238 				case SET_TOPOLOGY_REQ:
239 					error = tsmf_ifman_set_topology_request(&ifman);
240 					processed = TRUE;
241 					break;
242 
243 				case REMOVE_STREAM:
244 					error = tsmf_ifman_remove_stream(&ifman);
245 					processed = TRUE;
246 					break;
247 
248 				case SET_SOURCE_VIDEO_RECT:
249 					error = tsmf_ifman_set_source_video_rect(&ifman);
250 					processed = TRUE;
251 					break;
252 
253 				case SHUTDOWN_PRESENTATION_REQ:
254 					error = tsmf_ifman_shutdown_presentation(&ifman);
255 					processed = TRUE;
256 					break;
257 
258 				case ON_STREAM_VOLUME:
259 					error = tsmf_ifman_on_stream_volume(&ifman);
260 					processed = TRUE;
261 					break;
262 
263 				case ON_CHANNEL_VOLUME:
264 					error = tsmf_ifman_on_channel_volume(&ifman);
265 					processed = TRUE;
266 					break;
267 
268 				case SET_VIDEO_WINDOW:
269 					error = tsmf_ifman_set_video_window(&ifman);
270 					processed = TRUE;
271 					break;
272 
273 				case UPDATE_GEOMETRY_INFO:
274 					error = tsmf_ifman_update_geometry_info(&ifman);
275 					processed = TRUE;
276 					break;
277 
278 				case SET_ALLOCATOR:
279 					error = tsmf_ifman_set_allocator(&ifman);
280 					processed = TRUE;
281 					break;
282 
283 				case NOTIFY_PREROLL:
284 					error = tsmf_ifman_notify_preroll(&ifman);
285 					processed = TRUE;
286 					break;
287 
288 				case ON_SAMPLE:
289 					error = tsmf_ifman_on_sample(&ifman);
290 					processed = TRUE;
291 					break;
292 
293 				case ON_FLUSH:
294 					error = tsmf_ifman_on_flush(&ifman);
295 					processed = TRUE;
296 					break;
297 
298 				case ON_END_OF_STREAM:
299 					error = tsmf_ifman_on_end_of_stream(&ifman);
300 					processed = TRUE;
301 					break;
302 
303 				case ON_PLAYBACK_STARTED:
304 					error = tsmf_ifman_on_playback_started(&ifman);
305 					processed = TRUE;
306 					break;
307 
308 				case ON_PLAYBACK_PAUSED:
309 					error = tsmf_ifman_on_playback_paused(&ifman);
310 					processed = TRUE;
311 					break;
312 
313 				case ON_PLAYBACK_RESTARTED:
314 					error = tsmf_ifman_on_playback_restarted(&ifman);
315 					processed = TRUE;
316 					break;
317 
318 				case ON_PLAYBACK_STOPPED:
319 					error = tsmf_ifman_on_playback_stopped(&ifman);
320 					processed = TRUE;
321 					break;
322 
323 				case ON_PLAYBACK_RATE_CHANGED:
324 					error = tsmf_ifman_on_playback_rate_changed(&ifman);
325 					processed = TRUE;
326 					break;
327 
328 				case RIMCALL_RELEASE:
329 				case RIMCALL_QUERYINTERFACE:
330 					break;
331 
332 				default:
333 					break;
334 			}
335 
336 			break;
337 
338 		default:
339 			break;
340 	}
341 
342 	input = NULL;
343 	ifman.input = NULL;
344 
345 	if (error)
346 	{
347 		WLog_ERR(TAG, "ifman data received processing error %" PRIu32 "", error);
348 	}
349 
350 	if (!processed)
351 	{
352 		switch (FunctionId)
353 		{
354 			case RIMCALL_RELEASE:
355 				/* [MS-RDPEXPS] 2.2.2.2 Interface Release (IFACE_RELEASE)
356 				   This message does not require a reply. */
357 				processed = TRUE;
358 				ifman.output_pending = 1;
359 				break;
360 
361 			case RIMCALL_QUERYINTERFACE:
362 				/* [MS-RDPEXPS] 2.2.2.1.2 Query Interface Response (QI_RSP)
363 				   This message is not supported in this channel. */
364 				processed = TRUE;
365 				break;
366 		}
367 
368 		if (!processed)
369 		{
370 			WLog_ERR(TAG,
371 			         "Unknown InterfaceId: 0x%08" PRIX32 " MessageId: 0x%08" PRIX32
372 			         " FunctionId: 0x%08" PRIX32 "\n",
373 			         InterfaceId, MessageId, FunctionId);
374 			/* When a request is not implemented we return empty response indicating error */
375 		}
376 
377 		processed = TRUE;
378 	}
379 
380 	if (processed && !ifman.output_pending)
381 	{
382 		/* Response packet does not have FunctionId */
383 		length = Stream_GetPosition(output);
384 		Stream_SetPosition(output, 0);
385 		Stream_Write_UINT32(output, ifman.output_interface_id);
386 		Stream_Write_UINT32(output, MessageId);
387 		DEBUG_TSMF("response size %d", length);
388 		error = callback->channel->Write(callback->channel, length, Stream_Buffer(output), NULL);
389 
390 		if (error)
391 		{
392 			WLog_ERR(TAG, "response error %" PRIu32 "", error);
393 		}
394 	}
395 
396 out:
397 	Stream_Free(output, TRUE);
398 	return error;
399 }
400 
401 /**
402  * Function description
403  *
404  * @return 0 on success, otherwise a Win32 error code
405  */
tsmf_on_close(IWTSVirtualChannelCallback * pChannelCallback)406 static UINT tsmf_on_close(IWTSVirtualChannelCallback* pChannelCallback)
407 {
408 	TSMF_STREAM* stream;
409 	TSMF_PRESENTATION* presentation;
410 	TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
411 	DEBUG_TSMF("");
412 
413 	if (callback->stream_id)
414 	{
415 		presentation = tsmf_presentation_find_by_id(callback->presentation_id);
416 
417 		if (presentation)
418 		{
419 			stream = tsmf_stream_find_by_id(presentation, callback->stream_id);
420 
421 			if (stream)
422 				tsmf_stream_free(stream);
423 		}
424 	}
425 
426 	free(pChannelCallback);
427 	return CHANNEL_RC_OK;
428 }
429 
430 /**
431  * Function description
432  *
433  * @return 0 on success, otherwise a Win32 error code
434  */
tsmf_on_new_channel_connection(IWTSListenerCallback * pListenerCallback,IWTSVirtualChannel * pChannel,BYTE * Data,BOOL * pbAccept,IWTSVirtualChannelCallback ** ppCallback)435 static UINT tsmf_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
436                                            IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept,
437                                            IWTSVirtualChannelCallback** ppCallback)
438 {
439 	TSMF_CHANNEL_CALLBACK* callback;
440 	TSMF_LISTENER_CALLBACK* listener_callback = (TSMF_LISTENER_CALLBACK*)pListenerCallback;
441 	DEBUG_TSMF("");
442 	callback = (TSMF_CHANNEL_CALLBACK*)calloc(1, sizeof(TSMF_CHANNEL_CALLBACK));
443 
444 	if (!callback)
445 		return CHANNEL_RC_NO_MEMORY;
446 
447 	callback->iface.OnDataReceived = tsmf_on_data_received;
448 	callback->iface.OnClose = tsmf_on_close;
449 	callback->iface.OnOpen = NULL;
450 	callback->plugin = listener_callback->plugin;
451 	callback->channel_mgr = listener_callback->channel_mgr;
452 	callback->channel = pChannel;
453 	*ppCallback = (IWTSVirtualChannelCallback*)callback;
454 	return CHANNEL_RC_OK;
455 }
456 
457 /**
458  * Function description
459  *
460  * @return 0 on success, otherwise a Win32 error code
461  */
tsmf_plugin_initialize(IWTSPlugin * pPlugin,IWTSVirtualChannelManager * pChannelMgr)462 static UINT tsmf_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
463 {
464 	UINT status;
465 	TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
466 	DEBUG_TSMF("");
467 	tsmf->listener_callback = (TSMF_LISTENER_CALLBACK*)calloc(1, sizeof(TSMF_LISTENER_CALLBACK));
468 
469 	if (!tsmf->listener_callback)
470 		return CHANNEL_RC_NO_MEMORY;
471 
472 	tsmf->listener_callback->iface.OnNewChannelConnection = tsmf_on_new_channel_connection;
473 	tsmf->listener_callback->plugin = pPlugin;
474 	tsmf->listener_callback->channel_mgr = pChannelMgr;
475 	status = pChannelMgr->CreateListener(
476 	    pChannelMgr, "TSMF", 0, (IWTSListenerCallback*)tsmf->listener_callback, &(tsmf->listener));
477 	tsmf->listener->pInterface = tsmf->iface.pInterface;
478 	return status;
479 }
480 
481 /**
482  * Function description
483  *
484  * @return 0 on success, otherwise a Win32 error code
485  */
tsmf_plugin_terminated(IWTSPlugin * pPlugin)486 static UINT tsmf_plugin_terminated(IWTSPlugin* pPlugin)
487 {
488 	TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
489 	DEBUG_TSMF("");
490 	free(tsmf->listener_callback);
491 	free(tsmf);
492 	return CHANNEL_RC_OK;
493 }
494 
495 /**
496  * Function description
497  *
498  * @return 0 on success, otherwise a Win32 error code
499  */
tsmf_process_addin_args(IWTSPlugin * pPlugin,ADDIN_ARGV * args)500 static UINT tsmf_process_addin_args(IWTSPlugin* pPlugin, ADDIN_ARGV* args)
501 {
502 	int status;
503 	DWORD flags;
504 	COMMAND_LINE_ARGUMENT_A* arg;
505 	TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
506 	COMMAND_LINE_ARGUMENT_A tsmf_args[] = { { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
507 		                                      NULL, NULL, -1, NULL, "audio subsystem" },
508 		                                    { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL,
509 		                                      NULL, -1, NULL, "audio device name" },
510 		                                    { "decoder", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
511 		                                      NULL, NULL, -1, NULL, "decoder subsystem" },
512 		                                    { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
513 	flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
514 	status = CommandLineParseArgumentsA(args->argc, args->argv, tsmf_args, flags, tsmf, NULL, NULL);
515 
516 	if (status != 0)
517 		return ERROR_INVALID_DATA;
518 
519 	arg = tsmf_args;
520 
521 	do
522 	{
523 		if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
524 			continue;
525 
526 		CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys")
527 		{
528 			tsmf->audio_name = _strdup(arg->Value);
529 
530 			if (!tsmf->audio_name)
531 				return ERROR_OUTOFMEMORY;
532 		}
533 		CommandLineSwitchCase(arg, "dev")
534 		{
535 			tsmf->audio_device = _strdup(arg->Value);
536 
537 			if (!tsmf->audio_device)
538 				return ERROR_OUTOFMEMORY;
539 		}
540 		CommandLineSwitchCase(arg, "decoder")
541 		{
542 			tsmf->decoder_name = _strdup(arg->Value);
543 
544 			if (!tsmf->decoder_name)
545 				return ERROR_OUTOFMEMORY;
546 		}
547 		CommandLineSwitchDefault(arg)
548 		{
549 		}
550 		CommandLineSwitchEnd(arg)
551 	} while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
552 
553 	return CHANNEL_RC_OK;
554 }
555 
556 #ifdef BUILTIN_CHANNELS
557 #define DVCPluginEntry tsmf_DVCPluginEntry
558 #else
559 #define DVCPluginEntry FREERDP_API DVCPluginEntry
560 #endif
561 
562 /**
563  * Function description
564  *
565  * @return 0 on success, otherwise a Win32 error code
566  */
DVCPluginEntry(IDRDYNVC_ENTRY_POINTS * pEntryPoints)567 UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
568 {
569 	UINT status = 0;
570 	TSMF_PLUGIN* tsmf;
571 	TsmfClientContext* context;
572 	UINT error = CHANNEL_RC_NO_MEMORY;
573 	tsmf = (TSMF_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "tsmf");
574 
575 	if (!tsmf)
576 	{
577 		tsmf = (TSMF_PLUGIN*)calloc(1, sizeof(TSMF_PLUGIN));
578 
579 		if (!tsmf)
580 		{
581 			WLog_ERR(TAG, "calloc failed!");
582 			return CHANNEL_RC_NO_MEMORY;
583 		}
584 
585 		tsmf->iface.Initialize = tsmf_plugin_initialize;
586 		tsmf->iface.Connected = NULL;
587 		tsmf->iface.Disconnected = NULL;
588 		tsmf->iface.Terminated = tsmf_plugin_terminated;
589 		tsmf->rdpcontext =
590 		    ((freerdp*)((rdpSettings*)pEntryPoints->GetRdpSettings(pEntryPoints))->instance)
591 		        ->context;
592 		context = (TsmfClientContext*)calloc(1, sizeof(TsmfClientContext));
593 
594 		if (!context)
595 		{
596 			WLog_ERR(TAG, "calloc failed!");
597 			goto error_context;
598 		}
599 
600 		context->handle = (void*)tsmf;
601 		tsmf->iface.pInterface = (void*)context;
602 
603 		if (!tsmf_media_init())
604 		{
605 			error = ERROR_INVALID_OPERATION;
606 			goto error_init;
607 		}
608 
609 		status = pEntryPoints->RegisterPlugin(pEntryPoints, "tsmf", (IWTSPlugin*)tsmf);
610 	}
611 
612 	if (status == CHANNEL_RC_OK)
613 	{
614 		status =
615 		    tsmf_process_addin_args((IWTSPlugin*)tsmf, pEntryPoints->GetPluginData(pEntryPoints));
616 	}
617 
618 	return status;
619 error_init:
620 	free(context);
621 error_context:
622 	free(tsmf);
623 	return error;
624 }
625