23#include <freerdp/config.h>
25#include <winpr/cast.h>
27#include <winpr/print.h>
28#include <winpr/stream.h>
30#include "rdpei_main.h"
31#include "../rdpei_common.h"
32#include <freerdp/channels/rdpei.h>
33#include <freerdp/server/rdpei.h>
44 STATE_WAITING_CLIENT_READY,
49struct s_rdpei_server_private
58 eRdpEiChannelState channelState;
65 UINT16 currentMsgType;
70 enum RdpEiState automataState;
73static UINT rdpei_server_open_channel(RdpeiServerContext* context)
75 DWORD error = ERROR_SUCCESS;
76 DWORD bytesReturned = 0;
77 PULONG pSessionId =
nullptr;
80 WINPR_ASSERT(context);
82 RdpeiServerPrivate* priv = context->priv;
85 if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,
86 (LPSTR*)&pSessionId, &bytesReturned) == FALSE)
88 WLog_ERR(TAG,
"WTSQuerySessionInformationA failed!");
89 return ERROR_INTERNAL_ERROR;
92 DWORD sessionId = (DWORD)*pSessionId;
93 WTSFreeMemory(pSessionId);
96 WTSVirtualChannelOpenEx(sessionId, RDPEI_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
97 if (!priv->channelHandle)
99 error = GetLastError();
100 WLog_ERR(TAG,
"WTSVirtualChannelOpenEx failed with error %" PRIu32
"!", error);
104 const UINT32 channelId = WTSChannelGetIdByHandle(priv->channelHandle);
106 IFCALLRET(context->onChannelIdAssigned, status, context, channelId);
109 WLog_ERR(TAG,
"context->onChannelIdAssigned failed!");
110 return ERROR_INTERNAL_ERROR;
116static UINT rdpei_server_context_poll_int(RdpeiServerContext* context)
118 RdpeiServerPrivate* priv =
nullptr;
119 UINT error = ERROR_INTERNAL_ERROR;
121 WINPR_ASSERT(context);
122 priv = context->priv;
125 switch (priv->channelState)
128 error = rdpei_server_open_channel(context);
130 WLog_ERR(TAG,
"rdpei_server_open_channel failed with error %" PRIu32
"!", error);
132 priv->channelState = RDPEI_OPENED;
135 error = rdpei_server_handle_messages(context);
144static HANDLE rdpei_server_get_channel_handle(RdpeiServerContext* context)
146 RdpeiServerPrivate* priv =
nullptr;
147 void* buffer =
nullptr;
148 DWORD bytesReturned = 0;
149 HANDLE channelEvent =
nullptr;
151 WINPR_ASSERT(context);
152 priv = context->priv;
155 if (WTSVirtualChannelQuery(priv->channelHandle, WTSVirtualEventHandle, &buffer,
156 &bytesReturned) == TRUE)
158 if (bytesReturned ==
sizeof(HANDLE))
159 channelEvent = *(HANDLE*)buffer;
161 WTSFreeMemory(buffer);
167static DWORD WINAPI rdpei_server_thread_func(LPVOID arg)
169 RdpeiServerContext* context = (RdpeiServerContext*)arg;
170 RdpeiServerPrivate* priv =
nullptr;
171 HANDLE events[2] = WINPR_C_ARRAY_INIT;
173 UINT error = CHANNEL_RC_OK;
176 WINPR_ASSERT(context);
177 priv = context->priv;
180 events[nCount++] = priv->stopEvent;
182 while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
184 switch (priv->channelState)
187 error = rdpei_server_context_poll_int(context);
188 if (error == CHANNEL_RC_OK)
190 events[1] = rdpei_server_get_channel_handle(context);
195 status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
200 case WAIT_OBJECT_0 + 1:
202 error = rdpei_server_context_poll_int(context);
207 error = ERROR_INTERNAL_ERROR;
216 (void)WTSVirtualChannelClose(priv->channelHandle);
217 priv->channelHandle =
nullptr;
223static UINT rdpei_server_open(RdpeiServerContext* context)
225 RdpeiServerPrivate* priv =
nullptr;
227 priv = context->priv;
232 priv->stopEvent = CreateEvent(
nullptr, TRUE, FALSE,
nullptr);
233 if (!priv->stopEvent)
235 WLog_ERR(TAG,
"CreateEvent failed!");
236 return ERROR_INTERNAL_ERROR;
239 priv->thread = CreateThread(
nullptr, 0, rdpei_server_thread_func, context, 0,
nullptr);
242 WLog_ERR(TAG,
"CreateThread failed!");
243 (void)CloseHandle(priv->stopEvent);
244 priv->stopEvent =
nullptr;
245 return ERROR_INTERNAL_ERROR;
249 return CHANNEL_RC_OK;
252static UINT rdpei_server_close(RdpeiServerContext* context)
254 RdpeiServerPrivate* priv =
nullptr;
255 UINT error = CHANNEL_RC_OK;
257 priv = context->priv;
262 (void)SetEvent(priv->stopEvent);
264 if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
266 error = GetLastError();
267 WLog_ERR(TAG,
"WaitForSingleObject failed with error %" PRIu32
"", error);
271 (void)CloseHandle(priv->thread);
272 (void)CloseHandle(priv->stopEvent);
273 priv->thread =
nullptr;
274 priv->stopEvent =
nullptr;
280RdpeiServerContext* rdpei_server_context_new(HANDLE vcm)
282 RdpeiServerContext* ret = calloc(1,
sizeof(*ret));
287 ret->Open = rdpei_server_open;
288 ret->Close = rdpei_server_close;
290 ret->priv = calloc(1,
sizeof(*ret->priv));
294 ret->priv->inputStream = Stream_New(
nullptr, 256);
295 if (!ret->priv->inputStream)
298 ret->priv->outputStream = Stream_New(
nullptr, 200);
299 if (!ret->priv->outputStream)
303 rdpei_server_context_reset(ret);
307 WINPR_PRAGMA_DIAG_PUSH
308 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
309 rdpei_server_context_free(ret);
310 WINPR_PRAGMA_DIAG_POP
319UINT rdpei_server_init(RdpeiServerContext* context)
321 RdpeiServerPrivate* priv = context->priv;
322 UINT error = rdpei_server_open_channel(context);
326 priv->eventHandle = rdpei_server_get_channel_handle(context);
327 if (!priv->eventHandle)
329 WLog_ERR(TAG,
"Failed to get channel handle!");
333 return CHANNEL_RC_OK;
336 (void)WTSVirtualChannelClose(priv->channelHandle);
337 return CHANNEL_RC_INITIALIZATION_ERROR;
340void rdpei_server_context_reset(RdpeiServerContext* context)
342 RdpeiServerPrivate* priv = context->priv;
344 priv->channelHandle = INVALID_HANDLE_VALUE;
345 priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
346 priv->waitingHeaders = TRUE;
347 priv->automataState = STATE_INITIAL;
348 Stream_ResetPosition(priv->inputStream);
351void rdpei_server_context_free(RdpeiServerContext* context)
355 RdpeiServerPrivate* priv = context->priv;
358 if (priv->channelHandle && priv->channelHandle != INVALID_HANDLE_VALUE)
359 (void)WTSVirtualChannelClose(priv->channelHandle);
360 Stream_Free(priv->inputStream, TRUE);
361 Stream_Free(priv->outputStream, TRUE);
367HANDLE rdpei_server_get_event_handle(RdpeiServerContext* context)
369 return context->priv->eventHandle;
377static UINT read_cs_ready_message(RdpeiServerContext* context,
wStream* s)
379 UINT error = CHANNEL_RC_OK;
380 if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
381 return ERROR_INVALID_DATA;
383 Stream_Read_UINT32(s, context->protocolFlags);
384 Stream_Read_UINT32(s, context->clientVersion);
385 Stream_Read_UINT16(s, context->maxTouchPoints);
387 switch (context->clientVersion)
389 case RDPINPUT_PROTOCOL_V10:
390 case RDPINPUT_PROTOCOL_V101:
391 case RDPINPUT_PROTOCOL_V200:
392 case RDPINPUT_PROTOCOL_V300:
395 WLog_ERR(TAG,
"unhandled RPDEI protocol version 0x%" PRIx32
"", context->clientVersion);
399 IFCALLRET(context->onClientReady, error, context);
401 WLog_ERR(TAG,
"context->onClientReady failed with error %" PRIu32
"", error);
411static UINT read_touch_contact_data(RdpeiServerContext* context,
wStream* s,
414 WINPR_UNUSED(context);
415 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
416 return ERROR_INVALID_DATA;
418 Stream_Read_UINT8(s, contactData->contactId);
419 if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
420 !rdpei_read_4byte_signed(s, &contactData->x) ||
421 !rdpei_read_4byte_signed(s, &contactData->y) ||
422 !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
424 WLog_ERR(TAG,
"rdpei_read_ failed!");
425 return ERROR_INTERNAL_ERROR;
428 if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT)
430 if (!rdpei_read_2byte_signed(s, &contactData->contactRectLeft) ||
431 !rdpei_read_2byte_signed(s, &contactData->contactRectTop) ||
432 !rdpei_read_2byte_signed(s, &contactData->contactRectRight) ||
433 !rdpei_read_2byte_signed(s, &contactData->contactRectBottom))
435 WLog_ERR(TAG,
"rdpei_read_ failed!");
436 return ERROR_INTERNAL_ERROR;
440 if ((contactData->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) &&
441 !rdpei_read_4byte_unsigned(s, &contactData->orientation))
443 WLog_ERR(TAG,
"rdpei_read_ failed!");
444 return ERROR_INTERNAL_ERROR;
447 if ((contactData->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) &&
448 !rdpei_read_4byte_unsigned(s, &contactData->pressure))
450 WLog_ERR(TAG,
"rdpei_read_ failed!");
451 return ERROR_INTERNAL_ERROR;
454 return CHANNEL_RC_OK;
457static UINT read_pen_contact(RdpeiServerContext* context,
wStream* s,
460 WINPR_UNUSED(context);
461 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
462 return ERROR_INVALID_DATA;
464 Stream_Read_UINT8(s, contactData->deviceId);
465 if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
466 !rdpei_read_4byte_signed(s, &contactData->x) ||
467 !rdpei_read_4byte_signed(s, &contactData->y) ||
468 !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
470 WLog_ERR(TAG,
"rdpei_read_ failed!");
471 return ERROR_INTERNAL_ERROR;
474 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
476 if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags))
477 return ERROR_INVALID_DATA;
479 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
481 if (!rdpei_read_4byte_unsigned(s, &contactData->pressure))
482 return ERROR_INVALID_DATA;
484 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
486 if (!rdpei_read_2byte_unsigned(s, &contactData->rotation))
487 return ERROR_INVALID_DATA;
489 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
491 if (!rdpei_read_2byte_signed(s, &contactData->tiltX))
492 return ERROR_INVALID_DATA;
494 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
496 if (!rdpei_read_2byte_signed(s, &contactData->tiltY))
497 return ERROR_INVALID_DATA;
500 return CHANNEL_RC_OK;
513 if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
514 !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
516 WLog_ERR(TAG,
"rdpei_read_ failed!");
517 return ERROR_INTERNAL_ERROR;
521 if (!frame->contacts)
523 WLog_ERR(TAG,
"calloc failed!");
524 return CHANNEL_RC_NO_MEMORY;
527 for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
529 if ((error = read_touch_contact_data(context, s, contact)))
531 WLog_ERR(TAG,
"read_touch_contact_data failed with error %" PRIu32
"!", error);
532 frame->contactCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
533 touch_frame_reset(frame);
537 return CHANNEL_RC_OK;
545 if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
546 !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
548 WLog_ERR(TAG,
"rdpei_read_ failed!");
549 return ERROR_INTERNAL_ERROR;
553 if (!frame->contacts)
555 WLog_ERR(TAG,
"calloc failed!");
556 return CHANNEL_RC_NO_MEMORY;
559 for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
561 if ((error = read_pen_contact(context, s, contact)))
563 WLog_ERR(TAG,
"read_touch_contact_data failed with error %" PRIu32
"!", error);
564 frame->contactCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
566 pen_frame_reset(frame);
570 return CHANNEL_RC_OK;
578static UINT read_touch_event(RdpeiServerContext* context,
wStream* s)
580 UINT16 frameCount = 0;
582 WINPR_ASSERT(context);
583 WINPR_ASSERT(context->priv);
589 UINT error = CHANNEL_RC_OK;
591 if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
592 !rdpei_read_2byte_unsigned(s, &frameCount))
594 WLog_ERR(TAG,
"rdpei_read_ failed!");
595 return ERROR_INTERNAL_ERROR;
598 event->frameCount = frameCount;
602 WLog_ERR(TAG,
"calloc failed!");
603 return CHANNEL_RC_NO_MEMORY;
606 for (UINT32 i = 0; i < frameCount; i++, frame++)
608 if ((error = read_touch_frame(context, s, frame)))
610 WLog_ERR(TAG,
"read_touch_contact_data failed with error %" PRIu32
"!", error);
611 event->frameCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
617 IFCALLRET(context->onTouchEvent, error, context, event);
619 WLog_ERR(TAG,
"context->onTouchEvent failed with error %" PRIu32
"", error);
622 touch_event_reset(event);
626static UINT read_pen_event(RdpeiServerContext* context,
wStream* s)
628 UINT16 frameCount = 0;
631 UINT error = CHANNEL_RC_OK;
633 if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
634 !rdpei_read_2byte_unsigned(s, &frameCount))
636 WLog_ERR(TAG,
"rdpei_read_ failed!");
637 return ERROR_INTERNAL_ERROR;
640 event->frameCount = frameCount;
644 WLog_ERR(TAG,
"calloc failed!");
645 return CHANNEL_RC_NO_MEMORY;
648 for (UINT32 i = 0; i < frameCount; i++, frame++)
650 if ((error = read_pen_frame(context, s, frame)))
652 WLog_ERR(TAG,
"read_pen_frame failed with error %" PRIu32
"!", error);
653 event->frameCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
659 IFCALLRET(context->onPenEvent, error, context, event);
661 WLog_ERR(TAG,
"context->onPenEvent failed with error %" PRIu32
"", error);
664 pen_event_reset(event);
673static UINT read_dismiss_hovering_contact(RdpeiServerContext* context,
wStream* s)
676 UINT error = CHANNEL_RC_OK;
678 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
679 return ERROR_INVALID_DATA;
681 Stream_Read_UINT8(s, contactId);
683 IFCALLRET(context->onTouchReleased, error, context, contactId);
685 WLog_ERR(TAG,
"context->onTouchReleased failed with error %" PRIu32
"", error);
695UINT rdpei_server_handle_messages(RdpeiServerContext* context)
697 DWORD bytesReturned = 0;
699 WINPR_ASSERT(context);
701 RdpeiServerPrivate* priv = context->priv;
704 wStream* s = priv->inputStream;
705 UINT error = CHANNEL_RC_OK;
707 if (!WTSVirtualChannelRead(priv->channelHandle, 0, Stream_Pointer(s), priv->expectedBytes,
710 if (GetLastError() == ERROR_NO_DATA)
711 return ERROR_READ_FAULT;
713 WLog_DBG(TAG,
"channel connection closed");
714 return CHANNEL_RC_OK;
716 priv->expectedBytes -= bytesReturned;
717 Stream_Seek(s, bytesReturned);
719 if (priv->expectedBytes)
720 return CHANNEL_RC_OK;
722 Stream_SealLength(s);
723 Stream_ResetPosition(s);
725 if (priv->waitingHeaders)
728 priv->currentMsgType = Stream_Get_UINT16(s);
729 const UINT32 pduLen = Stream_Get_UINT32(s);
731 if (pduLen < RDPINPUT_HEADER_LENGTH)
733 WLog_ERR(TAG,
"invalid pduLength %" PRIu32
"", pduLen);
734 return ERROR_INVALID_DATA;
737 if (pduLen > RDPINPUT_MAX_PDU_LENGTH)
739 WLog_ERR(TAG,
"invalid pduLength %" PRIu32
" > RDPINPUT_MAX_PDU_LENGTH(%llu)", pduLen,
740 RDPINPUT_MAX_PDU_LENGTH);
741 return ERROR_INVALID_DATA;
744 priv->expectedBytes = pduLen - RDPINPUT_HEADER_LENGTH;
745 priv->waitingHeaders = FALSE;
746 Stream_ResetPosition(s);
747 if (priv->expectedBytes)
749 if (!Stream_EnsureCapacity(s, priv->expectedBytes))
751 WLog_ERR(TAG,
"Stream_EnsureCapacity failed!");
752 return CHANNEL_RC_NO_MEMORY;
754 return CHANNEL_RC_OK;
759 switch (priv->currentMsgType)
761 case EVENTID_CS_READY:
762 if (priv->automataState != STATE_WAITING_CLIENT_READY)
764 WLog_ERR(TAG,
"not expecting a CS_READY packet in this state(%u)",
765 priv->automataState);
766 return ERROR_INVALID_STATE;
769 if ((error = read_cs_ready_message(context, s)))
771 WLog_ERR(TAG,
"read_cs_ready_message failed with error %" PRIu32
"", error);
777 if ((error = read_touch_event(context, s)))
779 WLog_ERR(TAG,
"read_touch_event failed with error %" PRIu32
"", error);
783 case EVENTID_DISMISS_HOVERING_CONTACT:
784 if ((error = read_dismiss_hovering_contact(context, s)))
786 WLog_ERR(TAG,
"read_dismiss_hovering_contact failed with error %" PRIu32
"", error);
791 if ((error = read_pen_event(context, s)))
793 WLog_ERR(TAG,
"read_pen_event failed with error %" PRIu32
"", error);
798 WLog_ERR(TAG,
"unexpected message type 0x%" PRIx16
"", priv->currentMsgType);
801 Stream_ResetPosition(s);
802 priv->waitingHeaders = TRUE;
803 priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
812UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version, UINT32 features)
815 RdpeiServerPrivate* priv = context->priv;
818 if (priv->automataState != STATE_INITIAL)
820 WLog_ERR(TAG,
"called from unexpected state %u", priv->automataState);
821 return ERROR_INVALID_STATE;
824 Stream_ResetPosition(priv->outputStream);
826 if (version >= RDPINPUT_PROTOCOL_V300)
829 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen))
831 WLog_ERR(TAG,
"Stream_EnsureCapacity failed!");
832 return CHANNEL_RC_NO_MEMORY;
835 Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY);
836 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen);
837 Stream_Write_UINT32(priv->outputStream, version);
838 if (version >= RDPINPUT_PROTOCOL_V300)
839 Stream_Write_UINT32(priv->outputStream, features);
841 const size_t pos = Stream_GetPosition(priv->outputStream);
843 WINPR_ASSERT(pos <= UINT32_MAX);
844 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream,
char),
845 (ULONG)pos, &written))
847 WLog_ERR(TAG,
"WTSVirtualChannelWrite failed!");
848 return ERROR_INTERNAL_ERROR;
851 priv->automataState = STATE_WAITING_CLIENT_READY;
852 return CHANNEL_RC_OK;
860UINT rdpei_server_suspend(RdpeiServerContext* context)
863 RdpeiServerPrivate* priv = context->priv;
865 switch (priv->automataState)
867 case STATE_SUSPENDED:
868 WLog_ERR(TAG,
"already suspended");
869 return CHANNEL_RC_OK;
870 case STATE_WAITING_FRAME:
873 WLog_ERR(TAG,
"called from unexpected state %u", priv->automataState);
874 return ERROR_INVALID_STATE;
877 Stream_ResetPosition(priv->outputStream);
878 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
880 WLog_ERR(TAG,
"Stream_EnsureCapacity failed!");
881 return CHANNEL_RC_NO_MEMORY;
884 Stream_Write_UINT16(priv->outputStream, EVENTID_SUSPEND_TOUCH);
885 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
887 const size_t pos = Stream_GetPosition(priv->outputStream);
889 WINPR_ASSERT(pos <= UINT32_MAX);
890 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream,
char),
891 (ULONG)pos, &written))
893 WLog_ERR(TAG,
"WTSVirtualChannelWrite failed!");
894 return ERROR_INTERNAL_ERROR;
897 priv->automataState = STATE_SUSPENDED;
898 return CHANNEL_RC_OK;
906UINT rdpei_server_resume(RdpeiServerContext* context)
909 RdpeiServerPrivate* priv = context->priv;
911 switch (priv->automataState)
913 case STATE_WAITING_FRAME:
914 WLog_ERR(TAG,
"not suspended");
915 return CHANNEL_RC_OK;
916 case STATE_SUSPENDED:
919 WLog_ERR(TAG,
"called from unexpected state %u", priv->automataState);
920 return ERROR_INVALID_STATE;
923 Stream_ResetPosition(priv->outputStream);
924 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
926 WLog_ERR(TAG,
"Stream_EnsureCapacity failed!");
927 return CHANNEL_RC_NO_MEMORY;
930 Stream_Write_UINT16(priv->outputStream, EVENTID_RESUME_TOUCH);
931 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
933 const size_t pos = Stream_GetPosition(priv->outputStream);
935 WINPR_ASSERT(pos <= UINT32_MAX);
936 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream,
char),
937 (ULONG)pos, &written))
939 WLog_ERR(TAG,
"WTSVirtualChannelWrite failed!");
940 return ERROR_INTERNAL_ERROR;
943 priv->automataState = STATE_WAITING_FRAME;
944 return CHANNEL_RC_OK;